From b5d9e43e193ca751b26f126fd0d8e07365d433c6 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Tue, 19 Aug 2025 16:52:39 +1000 Subject: [PATCH 001/163] docs: Add the content of Fork & branch processing --- docs/contributing/contributing.md | 59 +++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index fa8fb538..88d3af06 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -10,6 +10,65 @@ First off, thanks for taking the time to contribute! If you have a general feedback about our project, please do not open an issue but instead please fill in this [form](https://forms.gle/LRUq3vsFnE1QCLiA6) +## Fork & branch processing + +First fork the origin repository to your own github repository, then clone the repository to your local computer. + +```bash +git clone https://github.com/Your Account name/clima.git +cd clima +``` + +Set up the upstream repository and check the output respositories. + +```bash +git remote add upstream https://github.com/CenterForTheBuiltEnvironment/clima.git + +git remote -v +``` + +The terminal should output a list: + +- `origin → your Fork repository` +- `upstream → origin repository` + +Check all branches. + +```bash +git branch -a +``` + +The terminal will show a list of branches: + +```bash +> * main + remotes/origin/HEAD -> origin/main + remotes/origin/development + remotes/origin/main +``` + +Pull the development branch first, and if the terminal does not notices you that you should try the second command. + +```bash +git checktout development + +git checkout -b development origin/development +``` + +Create a new branch in the development branch. + +```bash +git checkout -b (your branch name) +``` + +Finally update and push to your repository branch if you modify the files. + +```bash +git push origin (your branch name) +``` + + + ## Code of Conduct Available [here](code_of_conduct.md) From 852e350615f5a57c589d3f46786127833f40ac7a Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Tue, 19 Aug 2025 17:44:44 +1000 Subject: [PATCH 002/163] Add Code style and Testing --- docs/contributing/contributing.md | 32 +++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 88d3af06..9dbccdea 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -77,6 +77,38 @@ Available [here](code_of_conduct.md) We use Black.exe to format the code. +Install Black: + +```bash +pip install black +``` + +Format your code before committing: + +```bash +black . +``` + +## Testing + +Before submitting a Pull Request, please make sure: +- All tests should pass. +- Make sure you have installed project dependencies: + +```bash +npm install + +pip install -r requirements.txt +``` + +From the root directory, run: + +```bash +cd tests/node + +npx cypress run +``` + ## Submitting changes Please send a Pull Request with a clear list of what you've done. Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: From f9a72d031805889dd7c5189c4264282d50a02a12 Mon Sep 17 00:00:00 2001 From: Ziqi Liu Date: Tue, 19 Aug 2025 18:18:42 +1000 Subject: [PATCH 003/163] docs: Add the "Pull Request Regulation" part. --- docs/contributing/contributing.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 9dbccdea..c29715ee 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -67,6 +67,29 @@ Finally update and push to your repository branch if you modify the files. git push origin (your branch name) ``` +## Pull Request Regulation +1.Time to submit PR: +User requirements/issues have been addressed or discussed in Issue and consensus has been reached. +Changes have been minimised (small steps/phased submission) to avoid "mega PRs". + +2.Classification of Common PR Types: +a. Main (Master):Stable branch, merge code that passes review and CI; merge and release every time, +b. Develop: Continuous Integration branch for daily integration with multiple collaborators. +c. feature/*: feature development branch, cut out from main or develop, send PR to merge in after completing the feature. +d. Fix/*: defect repair branch, the same process as feature +e. Release/*: release preparation branch for freezing versions, fixing documentation, doing regressions and tagging. +f. docs/*, chore/*, refactor/*, test/*: documentation, miscellaneous, refactor, test type branches. +g. Style: style modification (does not affect the function): code formatting, space adjustment, naming rules unity. +h. Refactor: Code Refactoring: Refactor existing code to improve maintainability. +i. Test: Add or modify tests: add unit tests, integration tests, or modify test logic. +j. Chore: Build Configuration, Dependency Management, CI/CD Configuration Updates. +k. Perf: Performance Optimisation: Optimising code execution efficiency or memory usage. +l. Ci: CI Configuration Related: Changing Continuous Integration Configurations for Github Actions, Travis, Jenkins, etc. +m. Build: build system related: modify build scripts, packaging configuration. +n. Revert: Rollback Commit: Undoing a Previous Commit +o. Security: Security fixes, fixing security vulnerabilities, updating dependencies to prevent attacks. +p. Deps: Dependency Management: Dependency Management/Adding, updating, and removing dependency libraries +q. Infra: Infrastructure related: changes to development environments, containers, server configurations, etc. ## Code of Conduct From 9143a2ca32c24f8a6e849d668accefc7749fa077 Mon Sep 17 00:00:00 2001 From: Ziqi Liu Date: Tue, 19 Aug 2025 18:20:08 +1000 Subject: [PATCH 004/163] docs: Fixed the "pip" to "pipenv". --- docs/contributing/contributing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index c29715ee..0227bc12 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -103,7 +103,7 @@ We use Black.exe to format the code. Install Black: ```bash -pip install black +pipenv install black ``` Format your code before committing: @@ -121,7 +121,7 @@ Before submitting a Pull Request, please make sure: ```bash npm install -pip install -r requirements.txt +pipenv install -r requirements.txt ``` From the root directory, run: From 560ceeded255717a4939b868dbad7ba1c63aafe9 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Tue, 19 Aug 2025 18:37:32 +1000 Subject: [PATCH 005/163] docs: Fixed the content of the Pull Request Regulation format --- docs/contributing/contributing.md | 47 ++++++++++++++++--------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 0227bc12..d85d8ccd 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -68,28 +68,31 @@ git push origin (your branch name) ``` ## Pull Request Regulation -1.Time to submit PR: -User requirements/issues have been addressed or discussed in Issue and consensus has been reached. -Changes have been minimised (small steps/phased submission) to avoid "mega PRs". - -2.Classification of Common PR Types: -a. Main (Master):Stable branch, merge code that passes review and CI; merge and release every time, -b. Develop: Continuous Integration branch for daily integration with multiple collaborators. -c. feature/*: feature development branch, cut out from main or develop, send PR to merge in after completing the feature. -d. Fix/*: defect repair branch, the same process as feature -e. Release/*: release preparation branch for freezing versions, fixing documentation, doing regressions and tagging. -f. docs/*, chore/*, refactor/*, test/*: documentation, miscellaneous, refactor, test type branches. -g. Style: style modification (does not affect the function): code formatting, space adjustment, naming rules unity. -h. Refactor: Code Refactoring: Refactor existing code to improve maintainability. -i. Test: Add or modify tests: add unit tests, integration tests, or modify test logic. -j. Chore: Build Configuration, Dependency Management, CI/CD Configuration Updates. -k. Perf: Performance Optimisation: Optimising code execution efficiency or memory usage. -l. Ci: CI Configuration Related: Changing Continuous Integration Configurations for Github Actions, Travis, Jenkins, etc. -m. Build: build system related: modify build scripts, packaging configuration. -n. Revert: Rollback Commit: Undoing a Previous Commit -o. Security: Security fixes, fixing security vulnerabilities, updating dependencies to prevent attacks. -p. Deps: Dependency Management: Dependency Management/Adding, updating, and removing dependency libraries -q. Infra: Infrastructure related: changes to development environments, containers, server configurations, etc. +**Time to submit PR:** + +- User requirements/issues have been addressed or discussed in Issue and consensus has been reached. +- Changes have been minimised (small steps/phased submission) to avoid "mega PRs". + +**Classification of Common PR Types:** + +- `Main (Master)`: Stable branch, merge code that passes review and CI; merge and release every time, + +- `Develop`: Continuous Integration branch for daily integration with multiple collaborators. +- `Feature/*`: feature development branch, cut out from main or develop, send PR to merge in after completing the feature. +- `Fix/*`: defect repair branch, the same process as feature +- `Release/*`: release preparation branch for freezing versions, fixing documentation, doing regressions and tagging. +- `docs/*`, `chore/*`, `refactor/*`, `test/*`: documentation, miscellaneous, refactor, test type branches. +- `Style`: style modification (does not affect the function): code formatting, space adjustment, naming rules unity. +- `Refactor`: Code Refactoring: Refactor existing code to improve maintainability. +- `Test`: Add or modify tests: add unit tests, integration tests, or modify test logic. +- `Chore`: Build Configuration, Dependency Management, CI/CD Configuration Updates. +- `Perf`: Performance Optimisation: Optimising code execution efficiency or memory usage. +- `Ci`: CI Configuration Related: Changing Continuous Integration Configurations for Github Actions, Travis, Jenkins, etc. +- `Build`: build system related: modify build scripts, packaging configuration. +- `Revert`: Rollback Commit: Undoing a Previous Commit +- `Security`: Security fixes, fixing security vulnerabilities, updating dependencies to prevent attacks. +- `Deps`: Dependency Management: Dependency Management/Adding, updating, and removing dependency libraries +- `Infra`: Infrastructure related: changes to development environments, containers, server configurations, etc. ## Code of Conduct From 88dedfe4c1d24f50e16cd92b30fab46c5783695e Mon Sep 17 00:00:00 2001 From: FengW01 Date: Wed, 20 Aug 2025 14:33:48 +1000 Subject: [PATCH 006/163] docs(contributing): update introduction section of How to contribute guide --- docs/contributing/contributing.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 0227bc12..7eef07ef 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -5,6 +5,8 @@ description: Guide on how to contribute to this project # How to contribute First off, thanks for taking the time to contribute! +We use GitHub as our main collaboration platform. Please work from the `development` branch, create small feature branches, and open focused pull requests. Follow Conventional Commit messages (e.g., `feat:`, `fix:`, `docs:`), format Python code with Black, and add tests where needed. Never merge your own PR—wait for review and address all comments (including AI reviewer suggestions). Use Issues and Projects to track tasks and discussions. + ## General Feedback From 2fa3a606e0968c6dc7290eb117269d516d2265ec Mon Sep 17 00:00:00 2001 From: FengW01 Date: Wed, 20 Aug 2025 16:01:16 +1000 Subject: [PATCH 007/163] docs(contributing): update introduction section of How to contribute guide --- docs/contributing/contributing.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index d6de72eb..68413525 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -147,6 +147,10 @@ $ git commit -m "A brief summary of the commit > A paragraph describing what changed and its impact." ``` +## Hint +This project requires Python 3.11. Do not use Python 3.12 or newer, as it may cause dependency incompatibilities, build failure or runtime errors + + ## Thanks Thank you again for being interested in this project! You are awesome! From 48edb0e63473479ca78b363749a5e001eb133bbd Mon Sep 17 00:00:00 2001 From: FengW01 Date: Wed, 20 Aug 2025 16:11:08 +1000 Subject: [PATCH 008/163] docs: update contributing guide --- docs/contributing/contributing.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 68413525..b759057c 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -7,6 +7,8 @@ description: Guide on how to contribute to this project First off, thanks for taking the time to contribute! We use GitHub as our main collaboration platform. Please work from the `development` branch, create small feature branches, and open focused pull requests. Follow Conventional Commit messages (e.g., `feat:`, `fix:`, `docs:`), format Python code with Black, and add tests where needed. Never merge your own PR—wait for review and address all comments (including AI reviewer suggestions). Use Issues and Projects to track tasks and discussions. +> This project requires Python 3.11. Do not use Python 3.12 or newer, as it may cause dependency incompatibilities, build failure or runtime errors + ## General Feedback @@ -147,9 +149,6 @@ $ git commit -m "A brief summary of the commit > A paragraph describing what changed and its impact." ``` -## Hint -This project requires Python 3.11. Do not use Python 3.12 or newer, as it may cause dependency incompatibilities, build failure or runtime errors - ## Thanks From 730e00a082b0d8304b9b1d901f8eca32d2c98a83 Mon Sep 17 00:00:00 2001 From: Ziqi Liu Date: Wed, 20 Aug 2025 17:10:06 +1000 Subject: [PATCH 009/163] docs: A strategy module has been added to the Pull Request section, along with examples of code modifications. --- docs/contributing/contributing.md | 187 ++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index b759057c..af6c1a16 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -98,6 +98,193 @@ git push origin (your branch name) - `Deps`: Dependency Management: Dependency Management/Adding, updating, and removing dependency libraries - `Infra`: Infrastructure related: changes to development environments, containers, server configurations, etc. +**Pull Request Strategy** +- `Constant centralization`: Added `ElementIds/ComponentProperty/ClassNames` to `components.py`. Introduce `ElementIds/ComponentProperty` for all UI IDs, replace magic strings with constants. This enables single-source-of-truth and prevents typos. No business logic changed. Verified by running callback smoke tests and snapshot tests. This is an example of modifications made in the `t_rh.py` file. + +`Orginal t_rh.py file(extract)` +```python +@callback( + Output("yearly-chart", "children"), + [ + Input("df-store", "modified_timestamp"), + Input("global-local-radio-input", "value"), + Input("dropdown", "value"), + ], + [ + State("df-store", "data"), + State("meta-store", "data"), + State("si-ip-unit-store", "data"), + ], +) +``` +`Component.py: store repeated string` +```python +class ClassNames: + CONTAINER_COL = "container-col" + CONTAINER_COL_FULL_WIDTH = " container-col full-width" + TEXT_NEXT_TO_INPUT = "text-next-to-input" + + +class ElementIds: + DAILY = "daily" + DF_STORE = "df-store" + DROPDOWN = "dropdown" + HEATMAP = "heatmap" + GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + META_STORE = "meta-store" + SI_IP_UNIT_STORE = "si-ip-unit-store" + TABLE_TMP_HUM = "table-tmp-hum" + YEARLY_CHART = "yearly-chart" + + +class IdButtons: + DAILY_CHART_LABEL = "daily-chart-label" + + +class ComponentProperty: + CHILDREN = "children" + DATA = "data" + MODIFIED_TIMESTAMP = "modified_timestamp" + VALUE = "value" + + +class Text: + DAILY_CHART = "Daily chart" + YEARLY_CHART = "Yearly_chart" + HEATMAP_CHART = "Heatmap chart" + DESCRIPTIVE_STATISTICS = "Descriptive statistics" + + +class Type: + CIRCLE = "circle" +``` +`fixed t_rh.py(extract)` +```python +from components import ElementIds, ClassNames, Text, IdButtons, Type, ComponentProperty +@callback( + Output(ElementIds.YEARLY_CHART, ComponentProperty.CHILDREN), + [ + Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, ComponentProperty.VALUE), + Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), + ], + [ + State(ElementIds.DF_STORE, ComponentProperty.DATA), + State(ElementIds.META_STORE, ComponentProperty.DATA), + State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA), + ], +) +``` +- `Time field/timestamp harmonisation`: Unify time periods (such as hour, month) to `Colnames.hour` or `Colnames.month`. Added the `column_names.py` helper function and replaced the temporary parsing logic. Backward compatibility with old strings through a fault-tolerant parser; added round-trip testing in a multi-time zone environment. Below are three examples. + +`Create a new class to fix the time field` +```python +from enum import Enum + +class ColNames(str, Enum): + """ + DataFrame column name enumeration class, + avoiding hard-coded strings + """ + # Time-related columns + YEAR = "year" + MONTH = "month" + DAY = "day" + HOUR = "hour" + MINUTE = "minute" + + # Weather data column + DBT = "DBT" # dry bulb temperature + DPT = "DPT" # dew point temperature + RH = "RH" # relative humidity + P_ATM = "p_atm" # atmospheric pressure + + # Radiation-related column + EXTR_HOR_RAD = "extr_hor_rad" # Extraterrestrial Horizontal Radiation + HOR_IR_RAD = "hor_ir_rad" # Horizontal Infrared Radiation + GLOB_HOR_RAD = "glob_hor_rad" # Global Horizontal Radiation + DIR_NOR_RAD = "dir_nor_rad" # Direct Normal Radiation + DIF_HOR_RAD = "dif_hor_rad" # Diffuse Horizontal Radiation + + # Lighting-related columns + GLOB_HOR_ILL = "glob_hor_ill" # Global Horizontal Illuminance + DIR_NOR_ILL = "dir_nor_ill" # Direct Normal Illuminance + DIF_HOR_ILL = "dif_hor_ill" # Diffuse Horizontal Illuminance + + # Others + ZLUMI = "Zlumi" # Luminance + WIND_DIR = "wind_dir" # Wind Direction + WIND_SPEED = "wind_speed" # Wind Speed + TOT_SKY_COVER = "tot_sky_cover" # Total Sky Cover + OSKYCOVER = "Oskycover" # Opaque Sky Cover + VIS = "Vis" # Visibility + CHEIGHT = "Cheight" # Cloud Height + PWobs = "PWobs" # Precipitation Observation + PWcodes = "PWcodes" # Precipitation Codes + Pwater = "Pwater" # Precipitation Water + AsolOptD = "AsolOptD" # Aerosol Optical Depth + SnowD = "SnowD" # Snow Depth + DaySSnow = "DaySSnow" # Daily Snow + + # Calculation column + FAKE_YEAR = "fake_year" + MONTH_NAMES = "month_names" + UTC_TIME = "UTC_time" + DOY = "DOY" + ``` +`Example1` +```python +# Time filtering +# Before reconstruction +def violin(df, var, global_local, si_ip): + """Return day night violin based on the 'var' col""" + mask_day = (df["hour"] >= 8) & (df["hour"] < 20) + mask_night = (df["hour"] < 8) | (df["hour"] >= 20) + +# After reconstruction +from .column_names import ColNames + +def violin(df, var, global_local, si_ip): + """Return day night violin based on the 'var' col""" + mask_day = (df[ColNames.HOUR] >= 8) & (df[ColNames.HOUR] < 20) + mask_night = (df[ColNames.HOUR] < 8) | (df[ColNames.HOUR] >= 20) +``` +`Example2` +```python +# DataFrame grouping operations +# Before reconstruction +def daily_profile(df, var, global_local, si_ip): + var_month_ave = df.groupby(["month", "hour"])[var].median().reset_index() + + for i in range(12): + fig.add_trace( + go.Scatter( + x=df.loc[df["month"] == i + 1, "hour"], + y=df.loc[df["month"] == i + 1, var], + # ... other props + ) + ) + +# After reconstruction +from .column_names import ColNames + +def daily_profile(df, var, global_local, si_ip): + var_month_ave = df.groupby([ColNames.MONTH, ColNames.HOUR])[var] + .median() + .reset_index() + + for i in range(12): + fig.add_trace( + go.Scatter( + x=df.loc[df[ColNames.MONTH] == i + 1, ColNames.HOUR], + y=df.loc[df[ColNames.MONTH] == i + 1, var], + # ... other props + ) + ) +``` + +- `Progressive replacement/grey scale release`: Phase 1: Add constants; Phase 2: Batch replace references; Phase 3: Remove legacy code. The `FEATURE_USE_CONSTANT_IDS` feature switch allows for instant rollback. + ## Code of Conduct From 79db946c263aeb78b0495b930e7f07598d821231 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:28:32 +1000 Subject: [PATCH 010/163] feat: add Global Column Names class to replace string --- pages/lib/global_column_names.py | 48 ++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 pages/lib/global_column_names.py diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py new file mode 100644 index 00000000..f7e2c180 --- /dev/null +++ b/pages/lib/global_column_names.py @@ -0,0 +1,48 @@ +from enum import Enum + +class ColNames(str, Enum): + # ==================== Time related column ==================== + YEAR = "year" # year + MONTH = "month" # month + DAY = "day" # day + HOUR = "hour" # hour + MINUTE = "minute" # minute + + # ==================== Meteorological data column ==================== + DBT = "DBT" # Dry Bulb Temperature + DPT = "DPT" # Dew Point Temperature + RH = "RH" # Relative Humidity + P_ATM = "p_atm" # Atmospheric Pressure + + # ==================== Radiation-related column ==================== + EXTR_HOR_RAD = "extr_hor_rad" # Extraterrestrial Horizontal Radiation + HOR_IR_RAD = "hor_ir_rad" # Horizontal Infrared Radiation + GLOB_HOR_RAD = "glob_hor_rad" # Global Horizontal Radiation + DIR_NOR_RAD = "dir_nor_rad" # Direct Normal Radiation + DIF_HOR_RAD = "dif_hor_rad" # Diffuse Horizontal Radiation + + # ==================== Lighting-related columns ==================== + GLOB_HOR_ILL = "glob_hor_ill" # Global Horizontal Illuminance + DIR_NOR_ILL = "dir_nor_ill" # Direct Normal Illuminance + DIF_HOR_ILL = "dif_hor_ill" # Diffuse Horizontal Illuminance + + # ==================== Other ==================== + ZLUMI = "Zlumi" # Luminance + WIND_DIR = "wind_dir" # Wind Direction + WIND_SPEED = "wind_speed" # Wind Speed + TOT_SKY_COVER = "tot_sky_cover" # Total Sky Cover + OSKYCOVER = "Oskycover" # Opaque Sky Cover + VIS = "Vis" # Visibility + CHEIGHT = "Cheight" # Cloud Height + PWobs = "PWobs" # Precipitation Observation + PWcodes = "PWcodes" # Precipitation Codes + Pwater = "Pwater" # Precipitation Water + AsolOptD = "AsolOptD" # Aerosol Optical Depth + SnowD = "SnowD" # Snow Depth + DaySSnow = "DaySSnow" # Daily Snow + + # ==================== Calculation column ==================== + FAKE_YEAR = "fake_year" # Fake Year + MONTH_NAMES = "month_names" # Month names + UTC_TIME = "UTC_time" # UTC Time + DOY = "DOY" # Day of Year \ No newline at end of file From 01e95a61196ae81932d21a090b100641faa1cf24 Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Sat, 23 Aug 2025 14:30:50 +1000 Subject: [PATCH 011/163] code sanisitation(First version) --- pages/components.py | 33 +++++++++++++++++++ pages/t_rh.py | 78 ++++++++++++++++++++++----------------------- 2 files changed, 72 insertions(+), 39 deletions(-) create mode 100644 pages/components.py diff --git a/pages/components.py b/pages/components.py new file mode 100644 index 00000000..685de21e --- /dev/null +++ b/pages/components.py @@ -0,0 +1,33 @@ + + + +class ElementIds: + DAILY = "daily" + DF_STORE = "df-store" + DROPDOWN = "dropdown" + HEATMAP = "heatmap" + GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + META_STORE = "meta-store" + SI_IP_UNIT_STORE = "si-ip-unit-store" + TABLE_TMP_HUM = "table-tmp-hum" + YEARLY_CHART = "yearly-chart" + + +class IdButtons: + DAILY_CHART_LABEL = "daily-chart-label" + + +class ComponentProperty: + CHILDREN = "children" + DATA = "data" + MODIFIED_TIMESTAMP = "modified_timestamp" + VALUE = "value" + + +class Text: + DAILY_CHART = "Daily chart" + YEARLY_CHART = "Yearly_chart" + HEATMAP_CHART = "Heatmap chart" + DESCRIPTIVE_STATISTICS = "Descriptive statistics" +class Type: + CIRCLE = "circle" diff --git a/pages/t_rh.py b/pages/t_rh.py index c3d90f82..c3f04dfb 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,6 +1,6 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback - +from pages.components import ElementIds, Text, IdButtons, Type, ComponentProperty 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 @@ -37,7 +37,7 @@ def layout(): className="text-next-to-input", children=["Select a variable: "] ), dropdown( - id="dropdown", + id=ElementIds.DROPDOWN, className="dropdown-t-rh", options={var: dropdown_names[var] for var in var_to_plot}, value=dropdown_names[var_to_plot[0]], @@ -49,46 +49,46 @@ def layout(): children=[ html.Div( children=title_with_link( - text="Yearly chart", + text=Text.YEARLY_CHART, id_button="yearly-chart-label", doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( - type="circle", - children=html.Div(id="yearly-chart"), + type=Type.CIRCLE, + children=html.Div(id=ElementIds.YEARLY_CHART), ), html.Div( children=title_with_link( - text="Daily chart", - id_button="daily-chart-label", + text=Text.DAILY_CHART, + id_button=IdButtons.DAILY_CHART_LABEL, doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( - type="circle", - children=html.Div(id="daily"), + type=Type.CIRCLE, + children=html.Div(id=ElementIds.DAILY), ), html.Div( children=title_with_link( - text="Heatmap chart", + text=Text.HEATMAP_CHART, id_button="heatmap-chart-label", doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), dcc.Loading( - type="circle", - children=html.Div(id="heatmap"), + type=Type.CIRCLE, + children=html.Div(id=ElementIds.HEATMAP), ), html.Div( children=title_with_tooltip( - text="Descriptive statistics", + text=Text.DESCRIPTIVE_STATISTICS, tooltip_text="count, mean, std, min, max, and percentiles", id_button="table-tmp-rh", ), ), html.Div( - id="table-tmp-hum", + id=ElementIds.TABLE_TMP_HUM, ), ], ), @@ -97,16 +97,16 @@ def layout(): @callback( - Output("yearly-chart", "children"), + Output(ElementIds.YEARLY_CHART, ComponentProperty.CHILDREN), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), - Input("dropdown", "value"), + Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, ComponentProperty.VALUE), + Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.DF_STORE, ComponentProperty.DATA), + State(ElementIds.META_STORE, ComponentProperty.DATA), + State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA), ], ) def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): @@ -129,16 +129,16 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): @callback( - Output("daily", "children"), + Output(ElementIds.DAILY, ComponentProperty.CHILDREN), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), - Input("dropdown", "value"), + Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, ComponentProperty.VALUE), + Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.DF_STORE, ComponentProperty.DATA), + State(ElementIds.META_STORE, ComponentProperty.DATA), + State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA), ], ) def update_daily(_, global_local, dd_value, df, meta, si_ip): @@ -167,16 +167,16 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): @callback( - [Output("heatmap", "children")], + [Output(ElementIds.HEATMAP, ComponentProperty.CHILDREN)], [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), - Input("dropdown", "value"), + Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, ComponentProperty.VALUE), + Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.DF_STORE, ComponentProperty.DATA), + State(ElementIds.META_STORE, ComponentProperty.DATA), + State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA), ], ) def update_heatmap(_, global_local, dd_value, df, meta, si_ip): @@ -206,12 +206,12 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): @callback( - Output("table-tmp-hum", "children"), + Output(ElementIds.TABLE_TMP_HUM, ComponentProperty.CHILDREN), [ - Input("df-store", "modified_timestamp"), - Input("dropdown", "value"), + Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), + Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), ], - [State("df-store", "data"), State("si-ip-unit-store", "data")], + [State(ElementIds.DF_STORE, ComponentProperty.DATA), State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA)], ) def update_table(_, dd_value, df, si_ip): """Update the contents of tab three. Passing in general info (df, meta).""" From 0b20c6a997067e3285e24f979994ba7e361fa13c Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sat, 23 Aug 2025 14:41:31 +1000 Subject: [PATCH 012/163] fix: updated the comment --- pages/lib/global_column_names.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index f7e2c180..62a7a63d 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -26,7 +26,7 @@ class ColNames(str, Enum): DIR_NOR_ILL = "dir_nor_ill" # Direct Normal Illuminance DIF_HOR_ILL = "dif_hor_ill" # Diffuse Horizontal Illuminance - # ==================== Other ==================== + # ==================== Other columns ==================== ZLUMI = "Zlumi" # Luminance WIND_DIR = "wind_dir" # Wind Direction WIND_SPEED = "wind_speed" # Wind Speed From 84bb3ed0d7bee7ac39c0f314593c4b94911b256c Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Sat, 23 Aug 2025 15:06:07 +1000 Subject: [PATCH 013/163] code sanisitation(Version 2) --- pages/components.py | 33 ---------- pages/lib/global_column_names.py | 105 +++++++++++++++++++------------ pages/t_rh.py | 12 ++-- 3 files changed, 72 insertions(+), 78 deletions(-) delete mode 100644 pages/components.py diff --git a/pages/components.py b/pages/components.py deleted file mode 100644 index 685de21e..00000000 --- a/pages/components.py +++ /dev/null @@ -1,33 +0,0 @@ - - - -class ElementIds: - DAILY = "daily" - DF_STORE = "df-store" - DROPDOWN = "dropdown" - HEATMAP = "heatmap" - GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - META_STORE = "meta-store" - SI_IP_UNIT_STORE = "si-ip-unit-store" - TABLE_TMP_HUM = "table-tmp-hum" - YEARLY_CHART = "yearly-chart" - - -class IdButtons: - DAILY_CHART_LABEL = "daily-chart-label" - - -class ComponentProperty: - CHILDREN = "children" - DATA = "data" - MODIFIED_TIMESTAMP = "modified_timestamp" - VALUE = "value" - - -class Text: - DAILY_CHART = "Daily chart" - YEARLY_CHART = "Yearly_chart" - HEATMAP_CHART = "Heatmap chart" - DESCRIPTIVE_STATISTICS = "Descriptive statistics" -class Type: - CIRCLE = "circle" diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index 62a7a63d..35a3797e 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -1,48 +1,75 @@ from enum import Enum + class ColNames(str, Enum): # ==================== Time related column ==================== - YEAR = "year" # year - MONTH = "month" # month - DAY = "day" # day - HOUR = "hour" # hour - MINUTE = "minute" # minute - + YEAR = "year" # year + MONTH = "month" # month + DAY = "day" # day + HOUR = "hour" # hour + MINUTE = "minute" # minute + # ==================== Meteorological data column ==================== - DBT = "DBT" # Dry Bulb Temperature - DPT = "DPT" # Dew Point Temperature - RH = "RH" # Relative Humidity - P_ATM = "p_atm" # Atmospheric Pressure - + DBT = "DBT" # Dry Bulb Temperature + DPT = "DPT" # Dew Point Temperature + RH = "RH" # Relative Humidity + P_ATM = "p_atm" # Atmospheric Pressure + # ==================== Radiation-related column ==================== - EXTR_HOR_RAD = "extr_hor_rad" # Extraterrestrial Horizontal Radiation - HOR_IR_RAD = "hor_ir_rad" # Horizontal Infrared Radiation - GLOB_HOR_RAD = "glob_hor_rad" # Global Horizontal Radiation - DIR_NOR_RAD = "dir_nor_rad" # Direct Normal Radiation - DIF_HOR_RAD = "dif_hor_rad" # Diffuse Horizontal Radiation - + EXTR_HOR_RAD = "extr_hor_rad" # Extraterrestrial Horizontal Radiation + HOR_IR_RAD = "hor_ir_rad" # Horizontal Infrared Radiation + GLOB_HOR_RAD = "glob_hor_rad" # Global Horizontal Radiation + DIR_NOR_RAD = "dir_nor_rad" # Direct Normal Radiation + DIF_HOR_RAD = "dif_hor_rad" # Diffuse Horizontal Radiation + # ==================== Lighting-related columns ==================== - GLOB_HOR_ILL = "glob_hor_ill" # Global Horizontal Illuminance - DIR_NOR_ILL = "dir_nor_ill" # Direct Normal Illuminance - DIF_HOR_ILL = "dif_hor_ill" # Diffuse Horizontal Illuminance - + GLOB_HOR_ILL = "glob_hor_ill" # Global Horizontal Illuminance + DIR_NOR_ILL = "dir_nor_ill" # Direct Normal Illuminance + DIF_HOR_ILL = "dif_hor_ill" # Diffuse Horizontal Illuminance + # ==================== Other columns ==================== - ZLUMI = "Zlumi" # Luminance - WIND_DIR = "wind_dir" # Wind Direction - WIND_SPEED = "wind_speed" # Wind Speed - TOT_SKY_COVER = "tot_sky_cover" # Total Sky Cover - OSKYCOVER = "Oskycover" # Opaque Sky Cover - VIS = "Vis" # Visibility - CHEIGHT = "Cheight" # Cloud Height - PWobs = "PWobs" # Precipitation Observation - PWcodes = "PWcodes" # Precipitation Codes - Pwater = "Pwater" # Precipitation Water - AsolOptD = "AsolOptD" # Aerosol Optical Depth - SnowD = "SnowD" # Snow Depth - DaySSnow = "DaySSnow" # Daily Snow - + ZLUMI = "Zlumi" # Luminance + WIND_DIR = "wind_dir" # Wind Direction + WIND_SPEED = "wind_speed" # Wind Speed + TOT_SKY_COVER = "tot_sky_cover" # Total Sky Cover + OSKYCOVER = "Oskycover" # Opaque Sky Cover + VIS = "Vis" # Visibility + CHEIGHT = "Cheight" # Cloud Height + PWobs = "PWobs" # Precipitation Observation + PWcodes = "PWcodes" # Precipitation Codes + Pwater = "Pwater" # Precipitation Water + AsolOptD = "AsolOptD" # Aerosol Optical Depth + SnowD = "SnowD" # Snow Depth + DaySSnow = "DaySSnow" # Daily Snow + # ==================== Calculation column ==================== - FAKE_YEAR = "fake_year" # Fake Year - MONTH_NAMES = "month_names" # Month names - UTC_TIME = "UTC_time" # UTC Time - DOY = "DOY" # Day of Year \ No newline at end of file + FAKE_YEAR = "fake_year" # Fake Year + MONTH_NAMES = "month_names" # Month names + UTC_TIME = "UTC_time" # UTC Time + DOY = "DOY" # Day of Year + + +class ElementIds: + # ==================== Defines the unique ID constant for each element in the front-end page ==================== + DAILY = "daily" + DF_STORE = "df-store" + DROPDOWN = "dropdown" + HEATMAP = "heatmap" + GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + META_STORE = "meta-store" + SI_IP_UNIT_STORE = "si-ip-unit-store" + TABLE_TMP_HUM = "table-tmp-hum" + YEARLY_CHART = "yearly-chart" + + +class ComponentProperty: + # ==================== Define common attribute name constants for components ==================== + CHILDREN = "children" + DATA = "data" + MODIFIED_TIMESTAMP = "modified_timestamp" + VALUE = "value" + + +class Type: + # ==================== Defines type constants available for UI components ==================== + CIRCLE = "circle" diff --git a/pages/t_rh.py b/pages/t_rh.py index c3f04dfb..0a6477fe 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,6 +1,6 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback -from pages.components import ElementIds, Text, IdButtons, Type, ComponentProperty +from lib.global_column_names import ElementIds, Type, ComponentProperty 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 @@ -49,7 +49,7 @@ def layout(): children=[ html.Div( children=title_with_link( - text=Text.YEARLY_CHART, + text="Yearly_chart", id_button="yearly-chart-label", doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), @@ -60,8 +60,8 @@ def layout(): ), html.Div( children=title_with_link( - text=Text.DAILY_CHART, - id_button=IdButtons.DAILY_CHART_LABEL, + text="Daily chart", + id_button="daily-chart-label", doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), @@ -71,7 +71,7 @@ def layout(): ), html.Div( children=title_with_link( - text=Text.HEATMAP_CHART, + text="Heatmap chart", id_button="heatmap-chart-label", doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), @@ -82,7 +82,7 @@ def layout(): ), html.Div( children=title_with_tooltip( - text=Text.DESCRIPTIVE_STATISTICS, + text="Descriptive statistics", tooltip_text="count, mean, std, min, max, and percentiles", id_button="table-tmp-rh", ), From 825febb473c7d7154ca9c1765b03a77aa1a6665f Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sat, 23 Aug 2025 15:28:53 +1000 Subject: [PATCH 014/163] fix: replaced the string that using Class ColNames --- pages/t_rh.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pages/t_rh.py b/pages/t_rh.py index 0a6477fe..400c99f4 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,9 +1,9 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback -from lib.global_column_names import ElementIds, Type, ComponentProperty 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 +from pages.lib.global_column_names import ElementIds, Type, ComponentProperty, ColNames from pages.lib.utils import ( generate_chart_name, generate_units, @@ -111,7 +111,7 @@ def layout(): ) def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): if dd_value == dropdown_names[var_to_plot[0]]: - dbt_yearly = yearly_profile(df, "DBT", global_local, si_ip) + dbt_yearly = yearly_profile(df, ColNames.DBT, global_local, si_ip) dbt_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) units = generate_units_degree(si_ip) return dcc.Graph( @@ -119,7 +119,7 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): figure=dbt_yearly, ) else: - rh_yearly = yearly_profile(df, "RH", global_local, si_ip) + rh_yearly = yearly_profile(df, ColNames.RH, global_local, si_ip) rh_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) units = generate_units(si_ip) return dcc.Graph( @@ -147,8 +147,8 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("DryBulbTemperature_daily", meta, units), figure=daily_profile( - df[["DBT", "hour", "UTC_time", "month_names", "day", "month"]], - "DBT", + df[[ColNames.DBT, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY, ColNames.MONTH]], + ColNames.DBT, global_local, si_ip, ), @@ -158,8 +158,8 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("RelativeHumidity_daily", meta, units), figure=daily_profile( - df[["RH", "hour", "UTC_time", "month_names", "day", "month"]], - "RH", + df[[ColNames.RH, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY, ColNames.MONTH]], + ColNames.RH, global_local, si_ip, ), @@ -186,8 +186,8 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("DryBulbTemperature_heatmap", meta, units), figure=heatmap( - df[["DBT", "hour", "UTC_time", "month_names", "day"]], - "DBT", + df[[ColNames.DBT, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY]], + ColNames.DBT, global_local, si_ip, ), @@ -197,8 +197,8 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("RelativeHumidity_heatmap", meta, units), figure=heatmap( - df[["RH", "hour", "UTC_time", "month_names", "day"]], - "RH", + df[[ColNames.RH, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY]], + ColNames.RH, global_local, si_ip, ), @@ -216,5 +216,5 @@ 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).""" return summary_table_tmp_rh_tab( - df[["month", "hour", dd_value, "month_names"]], dd_value, si_ip + df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], dd_value, si_ip ) From b4a31daa33744fc8519833733ff74362330874e5 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:05:35 +1000 Subject: [PATCH 015/163] fix: replaced the string that using Class ColNames in natural_ventilation.py --- pages/natural_ventilation.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 32c62cdb..7dcfe8fa 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -18,6 +18,7 @@ container_col_center_one_of_three, ) from pages.lib.template_graphs import filter_df_by_month_and_hour +from pages.lib.global_column_names import ColNames from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -326,8 +327,8 @@ def nv_heatmap( month, hour, invert_month, invert_hour ) - var = "DBT" - filter_var = "DPT" + var = ColNames.DBT + filter_var = ColNames.DPT if dbt_data_filter and (min_dbt_val <= max_dbt_val): df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), var] = None @@ -335,7 +336,7 @@ def nv_heatmap( if dpt_data_filter: df.loc[(df[filter_var] < -200) | (df[filter_var] > max_dpt_val), var] = None - if df.dropna(subset=["month"]).shape[0] == 0: + if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return ( dbc.Alert( "Natural ventilation is not available in this location under these" @@ -386,15 +387,15 @@ def nv_heatmap( fig = go.Figure( data=go.Heatmap( - y=df["hour"] - 0.5, # Offset by 0.5 to center the hour labels - x=df["UTC_time"].dt.date, + y=df[ColNames.HOUR] - 0.5, # Offset by 0.5 to center the hour labels + x=df[ColNames.UTC_TIME].dt.date, z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], connectgaps=False, hoverongaps=False, - customdata=np.stack((df["month_names"], df["day"]), axis=-1), + customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), hovertemplate=( "" + var @@ -512,7 +513,7 @@ def nv_bar_chart( ) # this should be the total after filtering by time - tot_month_hours = df.groupby(df["UTC_time"].dt.month)["nv_allowed"].sum().values + tot_month_hours = df.groupby(df[ColNames.UTC_TIME].dt.month)["nv_allowed"].sum().values if dbt_data_filter and (min_dbt_val <= max_dbt_val): df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), "nv_allowed"] = 0 @@ -522,7 +523,7 @@ def nv_bar_chart( n_hours_nv_allowed = ( df.dropna(subset="nv_allowed") - .groupby(df["UTC_time"].dt.month)["nv_allowed"] + .groupby(df[ColNames.UTC_TIME].dt.month)["nv_allowed"] .sum() .values ) @@ -532,7 +533,7 @@ def nv_bar_chart( if len(normalize) == 0: fig = go.Figure( go.Bar( - x=df["month_names"].unique(), + x=df[ColNames.MONTH_NAMES].unique(), y=n_hours_nv_allowed, name="", marker_color=color_in, @@ -554,7 +555,7 @@ def nv_bar_chart( else: trace1 = go.Bar( - x=df["month_names"].unique(), + x=df[ColNames.MONTH_NAMES].unique(), y=per_time_nv_allowed, name="", marker_color=color_in, From d2f413921f4d4d3aac53cdb405be1105ac9b3b8e Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:32:05 +1000 Subject: [PATCH 016/163] fix: replaced the string that using Class ColNames in extract_df.py and template_graphs.py --- pages/lib/extract_df.py | 35 ++++++------ pages/lib/template_graphs.py | 100 +++++++++++++++++------------------ 2 files changed, 68 insertions(+), 67 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index a0def908..c4c9a3b0 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -18,6 +18,7 @@ from pages.lib.global_scheme import month_lst from pages.lib.utils import code_timer +from pages.lib.global_column_names import ColNames @code_timer @@ -148,7 +149,7 @@ def create_df(lst, file_name): # from EnergyPlus files extract info about reference years if not location_info["period"]: - years = epw_df["year"].astype("int").unique() + years = epw_df[ColNames.YEAR].astype("int").unique() if len(years) == 1: year_rounded_up = int(math.ceil(years[0] / 10.0)) * 10 location_info["period"] = f"{year_rounded_up - 10}-{year_rounded_up}" @@ -158,22 +159,22 @@ def create_df(lst, file_name): location_info["period"] = f"{min_year}-{max_year}" # Add fake_year - epw_df["fake_year"] = "year" + epw_df[ColNames.FAKE_YEAR] = ColNames.YEAR # Add in month names month_look_up = {ix + 1: month for ix, month in enumerate(month_lst)} - epw_df["month_names"] = epw_df["month"].astype("int").map(month_look_up) + epw_df[ColNames.MONTH_NAMES] = epw_df[ColNames.MONTH].astype("int").map(month_look_up) # Change to int type - epw_df[["year", "day", "month", "hour"]] = epw_df[ - ["year", "day", "month", "hour"] + epw_df[[ColNames.YEAR, ColNames.DAY, ColNames.MONTH, ColNames.HOUR]] = epw_df[ + [ColNames.YEAR, ColNames.DAY, ColNames.MONTH, ColNames.HOUR] ].astype(int) # Add in DOY - df_doy = epw_df.groupby(["month", "day"])["hour"].count().reset_index() - df_doy["DOY"] = df_doy.index + 1 + df_doy = epw_df.groupby([ColNames.MONTH, ColNames.DAY])[ColNames.HOUR].count().reset_index() + df_doy[ColNames.DOY] = df_doy.index + 1 epw_df = pd.merge( - epw_df, df_doy[["month", "day", "DOY"]], on=["month", "day"], how="left" + epw_df, df_doy[[ColNames.MONTH, ColNames.DAY, ColNames.DOY]], on=[ColNames.MONTH, ColNames.DAY], how="left" ) change_to_float = [ @@ -209,7 +210,7 @@ def create_df(lst, file_name): times = pd.date_range( "2019-01-01 00:00:00", "2020-01-01", inclusive="left", freq="h", tz="UTC" ) - epw_df["UTC_time"] = pd.to_datetime(times) + epw_df[ColNames.UTC_TIME] = pd.to_datetime(times) delta = timedelta(days=0, hours=location_info["time_zone"] - 1, minutes=0) times = times - delta epw_df["times"] = times @@ -251,8 +252,8 @@ def create_df(lst, file_name): epw_df = epw_df.join(mrt_df) - epw_df["MRT"] = epw_df["delta_mrt"] + epw_df["DBT"] - epw_df["wind_speed_utci"] = epw_df["wind_speed"] + epw_df["MRT"] = epw_df["delta_mrt"] + epw_df[ColNames.DBT] + epw_df["wind_speed_utci"] = epw_df[ColNames.WIND_SPEED] epw_df["wind_speed_utci"] = epw_df["wind_speed_utci"].mask( epw_df["wind_speed_utci"] >= 17, 16.9 ) @@ -263,16 +264,16 @@ def create_df(lst, file_name): epw_df["wind_speed_utci"] >= 0, 0.5 ) epw_df["utci_noSun_Wind"] = utci( - epw_df["DBT"], epw_df["DBT"], epw_df["wind_speed_utci"], epw_df["RH"] + epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df["wind_speed_utci"], epw_df[ColNames.RH] ) epw_df["utci_noSun_noWind"] = utci( - epw_df["DBT"], epw_df["DBT"], epw_df["wind_speed_utci_0"], epw_df["RH"] + epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df["wind_speed_utci_0"], epw_df[ColNames.RH] ) epw_df["utci_Sun_Wind"] = utci( - epw_df["DBT"], epw_df["MRT"], epw_df["wind_speed_utci"], epw_df["RH"] + epw_df[ColNames.DBT], epw_df["MRT"], epw_df["wind_speed_utci"], epw_df[ColNames.RH] ) epw_df["utci_Sun_noWind"] = utci( - epw_df["DBT"], epw_df["MRT"], epw_df["wind_speed_utci_0"], epw_df["RH"] + epw_df[ColNames.DBT], epw_df["MRT"], epw_df["wind_speed_utci_0"], epw_df[ColNames.RH] ) utci_bins = [-999, -40, -27, -13, 0, 9, 26, 32, 38, 46, 999] @@ -291,13 +292,13 @@ def create_df(lst, file_name): ) # Add psy values - ta_rh = np.vectorize(psy.psy_ta_rh)(epw_df["DBT"], epw_df["RH"]) + ta_rh = np.vectorize(psy.psy_ta_rh)(epw_df[ColNames.DBT], epw_df[ColNames.RH]) psy_df = pd.DataFrame.from_records(ta_rh) psy_df = psy_df.set_index(epw_df.times) epw_df = epw_df.join(psy_df) # calculate adaptive data - dbt_day_ave = epw_df.groupby(["DOY"])["DBT"].mean().to_list() + dbt_day_ave = epw_df.groupby([ColNames.DOY])[ColNames.DBT].mean().to_list() n = 7 epw_df["adaptive_comfort"] = np.nan epw_df["adaptive_cmf_80_low"] = np.nan diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 5f06e73c..e5acbe95 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -9,14 +9,14 @@ from pages.lib.global_scheme import mapping_dictionary import dash_bootstrap_components as dbc from .global_scheme import month_lst, template, tight_margins - +from pages.lib.global_column_names import ColNames from .utils import code_timer, determine_month_and_hour_filter def violin(df, var, global_local, si_ip): """Return day night violin based on the 'var' col""" - mask_day = (df["hour"] >= 8) & (df["hour"] < 20) - mask_night = (df["hour"] < 8) | (df["hour"] >= 20) + mask_day = (df[ColNames.HOUR] >= 8) & (df[ColNames.HOUR] < 20) + mask_night = (df[ColNames.HOUR] < 8) | (df[ColNames.HOUR] >= 20) var_unit = mapping_dictionary[var][si_ip]["unit"] var_range = mapping_dictionary[var][si_ip]["range"] var_name = mapping_dictionary[var]["name"] @@ -32,7 +32,7 @@ def violin(df, var, global_local, si_ip): fig = go.Figure() fig.add_trace( go.Violin( - x=df["fake_year"], + x=df[ColNames.FAKE_YEAR], y=data_day, line_color="#ffaa00", name="Day", @@ -44,7 +44,7 @@ def violin(df, var, global_local, si_ip): fig.add_trace( go.Violin( - x=df["fake_year"], + x=df[ColNames.FAKE_YEAR], y=data_night, line_color="#00264d", name="Night", @@ -107,14 +107,14 @@ def yearly_profile(df, var, global_local, si_ip): ) trace1 = go.Bar( - x=df["UTC_time"].dt.date.unique(), + x=df[ColNames.UTC_TIME].dt.date.unique(), y=dbt_day["max"] - dbt_day["min"], base=dbt_day["min"], marker_color=var_single_color, marker_opacity=0.3, name=var_name + " Range", customdata=np.stack( - (dbt_day["mean"], df.iloc[::24, :]["month_names"], df.iloc[::24, :]["day"]), + (dbt_day["mean"], df.iloc[::24, :][ColNames.MONTH_NAMES], df.iloc[::24, :][ColNames.DAY]), axis=-1, ), hovertemplate=( @@ -129,14 +129,14 @@ def yearly_profile(df, var, global_local, si_ip): ) trace2 = go.Scatter( - x=df["UTC_time"].dt.date.unique(), + x=df[ColNames.UTC_TIME].dt.date.unique(), y=dbt_day["mean"], name="Average " + var_name, mode="lines", marker_color=var_single_color, marker_opacity=1, customdata=np.stack( - (dbt_day["mean"], df.iloc[::24, :]["month_names"], df.iloc[::24, :]["day"]), + (dbt_day["mean"], df.iloc[::24, :][ColNames.MONTH_NAMES], df.iloc[::24, :][ColNames.DAY]), axis=-1, ), hovertemplate=( @@ -146,16 +146,16 @@ def yearly_profile(df, var, global_local, si_ip): ), ) - if var == "DBT": + if var == ColNames.DBT: # plot ashrae adaptive comfort limits (80%) - lo80 = df.groupby("DOY")["adaptive_cmf_80_low"].mean().values - hi80 = df.groupby("DOY")["adaptive_cmf_80_up"].mean().values - rmt = df.groupby("DOY")["adaptive_cmf_rmt"].mean().values + lo80 = df.groupby(ColNames.DOY)["adaptive_cmf_80_low"].mean().values + hi80 = df.groupby(ColNames.DOY)["adaptive_cmf_80_up"].mean().values + rmt = df.groupby(ColNames.DOY)["adaptive_cmf_rmt"].mean().values # set color https://github.com/CenterForTheBuiltEnvironment/clima/issues/113 implementation var_bar_colors = np.where((rmt > 40) | (rmt < 10), "lightgray", "darkgray") trace3 = go.Bar( - x=df["UTC_time"].dt.date.unique(), + x=df[ColNames.UTC_TIME].dt.date.unique(), y=hi80 - lo80, base=lo80, name="ASHRAE adaptive comfort (80%)", @@ -167,11 +167,11 @@ def yearly_profile(df, var, global_local, si_ip): ) # plot ashrae adaptive comfort limits (90%) - lo90 = df.groupby("DOY")["adaptive_cmf_90_low"].mean().values - hi90 = df.groupby("DOY")["adaptive_cmf_90_up"].mean().values + lo90 = df.groupby(ColNames.DOY)["adaptive_cmf_90_low"].mean().values + hi90 = df.groupby(ColNames.DOY)["adaptive_cmf_90_up"].mean().values trace4 = go.Bar( - x=df["UTC_time"].dt.date.unique(), + x=df[ColNames.UTC_TIME].dt.date.unique(), y=hi90 - lo90, base=lo90, name="ASHRAE adaptive comfort (90%)", @@ -183,7 +183,7 @@ def yearly_profile(df, var, global_local, si_ip): ) data = [trace3, trace4, trace1, trace2] - elif var == "RH": + elif var == ColNames.RH: # plot relative Humidity limits (30-70%) lo_rh = [30] * 365 hi_rh = [70] * 365 @@ -191,7 +191,7 @@ def yearly_profile(df, var, global_local, si_ip): hi_rh_df = pd.DataFrame({"hiRH": hi_rh}) trace3 = go.Bar( - x=df["UTC_time"].dt.date.unique(), + x=df[ColNames.UTC_TIME].dt.date.unique(), y=hi_rh_df["hiRH"] - lo_rh_df["loRH"], base=lo_rh_df["loRH"], name="humidity comfort band", @@ -252,7 +252,7 @@ def daily_profile(df, var, global_local, si_ip): range_y = [data_min, data_max] var_single_color = var_color[len(var_color) // 2] - var_month_ave = df.groupby(["month", "hour"])[var].median().reset_index() + var_month_ave = df.groupby([ColNames.MONTH, ColNames.HOUR])[var].median().reset_index() fig = make_subplots( rows=1, cols=12, @@ -263,15 +263,15 @@ def daily_profile(df, var, global_local, si_ip): for i in range(12): fig.add_trace( go.Scatter( - x=df.loc[df["month"] == i + 1, "hour"], - y=df.loc[df["month"] == i + 1, var], + x=df.loc[df[ColNames.MONTH] == i + 1, ColNames.HOUR], + y=df.loc[df[ColNames.MONTH] == i + 1, var], mode="markers", marker_color=var_single_color, opacity=0.5, marker_size=3, name=month_lst[i], showlegend=False, - customdata=df.loc[df["month"] == i + 1, "month_names"], + customdata=df.loc[df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES], hovertemplate=( "" + var @@ -286,8 +286,8 @@ def daily_profile(df, var, global_local, si_ip): fig.add_trace( go.Scatter( - x=var_month_ave.loc[var_month_ave["month"] == i + 1, "hour"], - y=var_month_ave.loc[var_month_ave["month"] == i + 1, var], + x=var_month_ave.loc[var_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR], + y=var_month_ave.loc[var_month_ave[ColNames.MONTH] == i + 1, var], mode="lines", line_color=var_single_color, line_width=3, @@ -343,7 +343,7 @@ def heatmap_with_filter( month, hour, invert_month, invert_hour ) - if df.dropna(subset=["month"]).shape[0] == 0: + if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return ( dbc.Alert( "No data is available in this location under these conditions. Please " @@ -364,13 +364,13 @@ def heatmap_with_filter( range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( - y=df["hour"] - 0.5, # Offset by 0.5 to center the hour labels - x=df["UTC_time"].dt.date, + y=df[ColNames.HOUR] - 0.5, # Offset by 0.5 to center the hour labels + x=df[ColNames.UTC_TIME].dt.date, z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], - customdata=np.stack((df["month_names"], df["day"]), axis=-1), + customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), hovertemplate=( "" + var @@ -423,13 +423,13 @@ def heatmap(df, var, global_local, si_ip): range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( - y=df["hour"], - x=df["UTC_time"].dt.date, + y=df[ColNames.HOUR], + x=df[ColNames.UTC_TIME].dt.date, z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], - customdata=np.stack((df["month_names"], df["day"]), axis=-1), + customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), hovertemplate=( "" + var @@ -478,16 +478,16 @@ def wind_rose(df, title, month, hour, labels, si_ip): start_hour = hour[0] end_hour = hour[1] if start_month <= end_month: - df = df.loc[(df["month"] >= start_month) & (df["month"] <= end_month)] + df = df.loc[(df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month)] else: - df = df.loc[(df["month"] <= end_month) | (df["month"] >= start_month)] + df = df.loc[(df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month)] if start_hour <= end_hour: - df = df.loc[(df["hour"] > start_hour) & (df["hour"] <= end_hour)] + df = df.loc[(df[ColNames.HOUR] > start_hour) & (df[ColNames.HOUR] <= end_hour)] else: - df = df.loc[(df["hour"] <= end_hour) | (df["hour"] >= start_hour)] + df = df.loc[(df[ColNames.HOUR] <= end_hour) | (df[ColNames.HOUR] >= start_hour)] - spd_colors = mapping_dictionary["wind_speed"]["color"] - spd_unit = mapping_dictionary["wind_speed"][si_ip]["unit"] + spd_colors = mapping_dictionary[ColNames.WIND_SPEED]["color"] + spd_unit = mapping_dictionary[ColNames.WIND_SPEED][si_ip]["unit"] spd_bins = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] if si_ip == UnitSystem.IP: spd_bins = convert_bins(spd_bins) @@ -501,10 +501,10 @@ def wind_rose(df, title, month, hour, labels, si_ip): # Create a temporary DataFrame with binned data df_binned = df.assign( WindSpd_bins=lambda d: pd.cut( - d["wind_speed"], bins=spd_bins, labels=spd_labels, right=True + d[ColNames.WIND_SPEED], bins=spd_bins, labels=spd_labels, right=True ), WindDir_bins=lambda d: pd.cut( - d["wind_dir"], bins=dir_bins, labels=dir_labels, right=False + d[ColNames.WIND_DIR], bins=dir_bins, labels=dir_labels, right=False ), ) @@ -618,7 +618,7 @@ def thermal_stress_stacked_barchart( month, hour, invert_month, invert_hour ) - if df.dropna(subset=["month"]).shape[0] == 0: + if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return ( dbc.Alert( "No data is available in this location under these conditions. Please " @@ -631,12 +631,12 @@ def thermal_stress_stacked_barchart( isNormalized = True if len(normalize) != 0 else False if isNormalized: new_df = ( - df.groupby("month")[var].value_counts(normalize=True).unstack(var).fillna(0) + df.groupby(ColNames.MONTH)[var].value_counts(normalize=True).unstack(var).fillna(0) ) new_df = new_df.set_axis(categories, axis=1) new_df.reset_index(inplace=True) else: - new_df = df.groupby("month")[var].value_counts().unstack(var).fillna(0) + new_df = df.groupby(ColNames.MONTH)[var].value_counts().unstack(var).fillna(0) new_df = new_df.set_axis(categories, axis=1) new_df.reset_index(inplace=True) @@ -740,13 +740,13 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): query = ( f"month=={str(i)} and ({filter_var}>={min_val} and {filter_var}<={max_val})" ) - a = new_df.query(query)["DOY"].count() + a = new_df.query(query)[ColNames.DOY].count() month_in.append(a) query = f"month=={str(i)} and ({filter_var}<{min_val})" - b = new_df.query(query)["DOY"].count() + b = new_df.query(query)[ColNames.DOY].count() month_below.append(b) query = f"month=={str(i)} and {filter_var}>{max_val}" - c = new_df.query(query)["DOY"].count() + c = new_df.query(query)[ColNames.DOY].count() month_above.append(c) go.Figure() @@ -830,17 +830,17 @@ def filter_df_by_month_and_hour( if time_filter: if start_month <= end_month: - mask = (df["month"] < start_month) | (df["month"] > end_month) + mask = (df[ColNames.MONTH] < start_month) | (df[ColNames.MONTH] > end_month) df.loc[mask, var] = None else: - mask = (df["month"] >= end_month) & (df["month"] <= start_month) + mask = (df[ColNames.MONTH] >= end_month) & (df[ColNames.MONTH] <= start_month) df.loc[mask, var] = None if start_hour <= end_hour: - mask = (df["hour"] <= start_hour) | (df["hour"] > end_hour) + mask = (df[ColNames.HOUR] <= start_hour) | (df[ColNames.HOUR] > end_hour) df.loc[mask, var] = None else: - mask = (df["hour"] > end_hour) & (df["hour"] <= start_hour) + mask = (df[ColNames.HOUR] > end_hour) & (df[ColNames.HOUR] <= start_hour) df.loc[mask, var] = None return df From 9b3ccaccc4a8f234bd0f971e23742d40f3add967 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Sat, 23 Aug 2025 16:36:50 +1000 Subject: [PATCH 017/163] docs: update pull request regulation of contributing.md --- docs/contributing/contributing.md | 257 +++++------------------------- 1 file changed, 39 insertions(+), 218 deletions(-) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index af6c1a16..6fc210af 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -23,7 +23,7 @@ git clone https://github.com/Your Account name/clima.git cd clima ``` -Set up the upstream repository and check the output respositories. +Set up the upstream repository and check the output repositories. ```bash git remote add upstream https://github.com/CenterForTheBuiltEnvironment/clima.git @@ -51,7 +51,7 @@ The terminal will show a list of branches: remotes/origin/main ``` -Pull the development branch first, and if the terminal does not notices you that you should try the second command. +Pull the development branch first, and if the terminal does not notice you that you should try the second command. ```bash git checktout development @@ -71,221 +71,6 @@ Finally update and push to your repository branch if you modify the files. git push origin (your branch name) ``` -## Pull Request Regulation -**Time to submit PR:** - -- User requirements/issues have been addressed or discussed in Issue and consensus has been reached. -- Changes have been minimised (small steps/phased submission) to avoid "mega PRs". - -**Classification of Common PR Types:** - -- `Main (Master)`: Stable branch, merge code that passes review and CI; merge and release every time, - -- `Develop`: Continuous Integration branch for daily integration with multiple collaborators. -- `Feature/*`: feature development branch, cut out from main or develop, send PR to merge in after completing the feature. -- `Fix/*`: defect repair branch, the same process as feature -- `Release/*`: release preparation branch for freezing versions, fixing documentation, doing regressions and tagging. -- `docs/*`, `chore/*`, `refactor/*`, `test/*`: documentation, miscellaneous, refactor, test type branches. -- `Style`: style modification (does not affect the function): code formatting, space adjustment, naming rules unity. -- `Refactor`: Code Refactoring: Refactor existing code to improve maintainability. -- `Test`: Add or modify tests: add unit tests, integration tests, or modify test logic. -- `Chore`: Build Configuration, Dependency Management, CI/CD Configuration Updates. -- `Perf`: Performance Optimisation: Optimising code execution efficiency or memory usage. -- `Ci`: CI Configuration Related: Changing Continuous Integration Configurations for Github Actions, Travis, Jenkins, etc. -- `Build`: build system related: modify build scripts, packaging configuration. -- `Revert`: Rollback Commit: Undoing a Previous Commit -- `Security`: Security fixes, fixing security vulnerabilities, updating dependencies to prevent attacks. -- `Deps`: Dependency Management: Dependency Management/Adding, updating, and removing dependency libraries -- `Infra`: Infrastructure related: changes to development environments, containers, server configurations, etc. - -**Pull Request Strategy** -- `Constant centralization`: Added `ElementIds/ComponentProperty/ClassNames` to `components.py`. Introduce `ElementIds/ComponentProperty` for all UI IDs, replace magic strings with constants. This enables single-source-of-truth and prevents typos. No business logic changed. Verified by running callback smoke tests and snapshot tests. This is an example of modifications made in the `t_rh.py` file. - -`Orginal t_rh.py file(extract)` -```python -@callback( - Output("yearly-chart", "children"), - [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), - Input("dropdown", "value"), - ], - [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), - ], -) -``` -`Component.py: store repeated string` -```python -class ClassNames: - CONTAINER_COL = "container-col" - CONTAINER_COL_FULL_WIDTH = " container-col full-width" - TEXT_NEXT_TO_INPUT = "text-next-to-input" - - -class ElementIds: - DAILY = "daily" - DF_STORE = "df-store" - DROPDOWN = "dropdown" - HEATMAP = "heatmap" - GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - META_STORE = "meta-store" - SI_IP_UNIT_STORE = "si-ip-unit-store" - TABLE_TMP_HUM = "table-tmp-hum" - YEARLY_CHART = "yearly-chart" - - -class IdButtons: - DAILY_CHART_LABEL = "daily-chart-label" - - -class ComponentProperty: - CHILDREN = "children" - DATA = "data" - MODIFIED_TIMESTAMP = "modified_timestamp" - VALUE = "value" - - -class Text: - DAILY_CHART = "Daily chart" - YEARLY_CHART = "Yearly_chart" - HEATMAP_CHART = "Heatmap chart" - DESCRIPTIVE_STATISTICS = "Descriptive statistics" - - -class Type: - CIRCLE = "circle" -``` -`fixed t_rh.py(extract)` -```python -from components import ElementIds, ClassNames, Text, IdButtons, Type, ComponentProperty -@callback( - Output(ElementIds.YEARLY_CHART, ComponentProperty.CHILDREN), - [ - Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, ComponentProperty.VALUE), - Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), - ], - [ - State(ElementIds.DF_STORE, ComponentProperty.DATA), - State(ElementIds.META_STORE, ComponentProperty.DATA), - State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA), - ], -) -``` -- `Time field/timestamp harmonisation`: Unify time periods (such as hour, month) to `Colnames.hour` or `Colnames.month`. Added the `column_names.py` helper function and replaced the temporary parsing logic. Backward compatibility with old strings through a fault-tolerant parser; added round-trip testing in a multi-time zone environment. Below are three examples. - -`Create a new class to fix the time field` -```python -from enum import Enum - -class ColNames(str, Enum): - """ - DataFrame column name enumeration class, - avoiding hard-coded strings - """ - # Time-related columns - YEAR = "year" - MONTH = "month" - DAY = "day" - HOUR = "hour" - MINUTE = "minute" - - # Weather data column - DBT = "DBT" # dry bulb temperature - DPT = "DPT" # dew point temperature - RH = "RH" # relative humidity - P_ATM = "p_atm" # atmospheric pressure - - # Radiation-related column - EXTR_HOR_RAD = "extr_hor_rad" # Extraterrestrial Horizontal Radiation - HOR_IR_RAD = "hor_ir_rad" # Horizontal Infrared Radiation - GLOB_HOR_RAD = "glob_hor_rad" # Global Horizontal Radiation - DIR_NOR_RAD = "dir_nor_rad" # Direct Normal Radiation - DIF_HOR_RAD = "dif_hor_rad" # Diffuse Horizontal Radiation - - # Lighting-related columns - GLOB_HOR_ILL = "glob_hor_ill" # Global Horizontal Illuminance - DIR_NOR_ILL = "dir_nor_ill" # Direct Normal Illuminance - DIF_HOR_ILL = "dif_hor_ill" # Diffuse Horizontal Illuminance - - # Others - ZLUMI = "Zlumi" # Luminance - WIND_DIR = "wind_dir" # Wind Direction - WIND_SPEED = "wind_speed" # Wind Speed - TOT_SKY_COVER = "tot_sky_cover" # Total Sky Cover - OSKYCOVER = "Oskycover" # Opaque Sky Cover - VIS = "Vis" # Visibility - CHEIGHT = "Cheight" # Cloud Height - PWobs = "PWobs" # Precipitation Observation - PWcodes = "PWcodes" # Precipitation Codes - Pwater = "Pwater" # Precipitation Water - AsolOptD = "AsolOptD" # Aerosol Optical Depth - SnowD = "SnowD" # Snow Depth - DaySSnow = "DaySSnow" # Daily Snow - - # Calculation column - FAKE_YEAR = "fake_year" - MONTH_NAMES = "month_names" - UTC_TIME = "UTC_time" - DOY = "DOY" - ``` -`Example1` -```python -# Time filtering -# Before reconstruction -def violin(df, var, global_local, si_ip): - """Return day night violin based on the 'var' col""" - mask_day = (df["hour"] >= 8) & (df["hour"] < 20) - mask_night = (df["hour"] < 8) | (df["hour"] >= 20) - -# After reconstruction -from .column_names import ColNames - -def violin(df, var, global_local, si_ip): - """Return day night violin based on the 'var' col""" - mask_day = (df[ColNames.HOUR] >= 8) & (df[ColNames.HOUR] < 20) - mask_night = (df[ColNames.HOUR] < 8) | (df[ColNames.HOUR] >= 20) -``` -`Example2` -```python -# DataFrame grouping operations -# Before reconstruction -def daily_profile(df, var, global_local, si_ip): - var_month_ave = df.groupby(["month", "hour"])[var].median().reset_index() - - for i in range(12): - fig.add_trace( - go.Scatter( - x=df.loc[df["month"] == i + 1, "hour"], - y=df.loc[df["month"] == i + 1, var], - # ... other props - ) - ) - -# After reconstruction -from .column_names import ColNames - -def daily_profile(df, var, global_local, si_ip): - var_month_ave = df.groupby([ColNames.MONTH, ColNames.HOUR])[var] - .median() - .reset_index() - - for i in range(12): - fig.add_trace( - go.Scatter( - x=df.loc[df[ColNames.MONTH] == i + 1, ColNames.HOUR], - y=df.loc[df[ColNames.MONTH] == i + 1, var], - # ... other props - ) - ) -``` - -- `Progressive replacement/grey scale release`: Phase 1: Add constants; Phase 2: Batch replace references; Phase 3: Remove legacy code. The `FEATURE_USE_CONSTANT_IDS` feature switch allows for instant rollback. - - ## Code of Conduct Available [here](code_of_conduct.md) @@ -310,7 +95,7 @@ black . Before submitting a Pull Request, please make sure: - All tests should pass. -- Make sure you have installed project dependencies: +- You have installed project dependencies: ```bash npm install @@ -336,6 +121,42 @@ $ git commit -m "A brief summary of the commit > A paragraph describing what changed and its impact." ``` +> Detailed requirements for submitting a PR are described in the [Pull Request Regulation](#pull-request-regulation) section below + +Classification of Common Commit Types: + +- `Main (Master)`: Stable branch, merge code that passes review and CI; merge and release every time, +- `Develop`: Continuous Integration branch for daily integration with multiple collaborators. +- `Feature/*`: feature development branch, cut out from main or develop, send PR to merge in after completing the feature. +- `Fix/*`: defect repair branch, the same process as feature +- `Release/*`: release preparation branch for freezing versions, fixing documentation, doing regressions and tagging. +- `docs/*`, `chore/*`, `refactor/*`, `test/*`: documentation, miscellaneous, refactor, test type branches. +- `Style`: style modification (does not affect the function): code formatting, space adjustment, naming rules unity. +- `Refactor`: Code Refactoring: Refactor existing code to improve maintainability. +- `Test`: Add or modify tests: add unit tests, integration tests, or modify test logic. +- `Chore`: Build Configuration, Dependency Management, CI/CD Configuration Updates. +- `Perf`: Performance Optimisation: Optimising code execution efficiency or memory usage. +- `Ci`: CI Configuration Related: Changing Continuous Integration Configurations for Github Actions, Travis, Jenkins, etc. +- `Build`: build system related: modify build scripts, packaging configuration. +- `Revert`: Rollback Commit: Undoing a Previous Commit +- `Security`: Security fixes, fixing security vulnerabilities, updating dependencies to prevent attacks. +- `Deps`: Dependency Management: Dependency Management/Adding, updating, and removing dependency libraries +- `Infra`: Infrastructure related: changes to development environments, containers, server configurations, etc. + +## Pull Request Regulation +**Time to submit PR:** + +- User requirements/issues have been addressed or discussed in Issue and consensus has been reached. +- Changes have been minimised (small steps/phased submission) to avoid "mega PRs". + +**The pull request should include the following information:** + +- **Description:** Provide a brief summary of the changes, related issues, and motivation. List any required dependencies. **Fixes # (issue)** + +- **Type of Change:** Bug fix (non-breaking); New feature (non-breaking); Breaking change; Documentation update. + +- **Testing:** Describe how you tested your changes and how we can reproduce them. Include test details if necessary. + ## Thanks From 38e4da19a4bc1aa2113aefa539de15a7b6a292e6 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Sat, 23 Aug 2025 16:38:19 +1000 Subject: [PATCH 018/163] fix: replaced the string that using Class ColNames in wind.py and charts_data_explorer.py --- pages/lib/charts_data_explorer.py | 9 +++++---- pages/wind.py | 31 ++++++++++++++++--------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index 2a085bd3..d1534057 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -5,6 +5,7 @@ import plotly.express as px import plotly.graph_objects as go from pages.lib.global_scheme import template, mapping_dictionary, month_lst +from pages.lib.global_column_names import ColNames def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si_ip): @@ -60,15 +61,15 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si fig = go.Figure( data=go.Heatmap( - y=df["hour"], - x=df["DOY"], + y=df[ColNames.HOUR], + x=df[ColNames.DOY], z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], connectgaps=False, hoverongaps=False, - customdata=np.stack((df["month_names"], df["day"]), axis=-1), + customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), hovertemplate=( "" + var @@ -132,7 +133,7 @@ def three_var_graph( else: df.loc[(df[filter_var] >= max_val) & (df[filter_var] <= min_val)] = None - if df.dropna(subset=["month"]).shape[0] == 0: + if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return None title = ( diff --git a/pages/wind.py b/pages/wind.py index ff90ce5a..fe0d3da8 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -5,6 +5,7 @@ from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import month_lst, container_row_center_full from pages.lib.template_graphs import heatmap, wind_rose +from pages.lib.global_column_names import ColNames from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -376,7 +377,7 @@ def update_annual_wind_rose(_, df, meta, si_ip): # wind speed @callback( - Output("wind-speed", "children"), + Output(ColNames.WIND_SPEED, "children"), # General [ Input("df-store", "modified_timestamp"), @@ -391,10 +392,10 @@ 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, "wind_speed", global_local, si_ip) + speed = heatmap(df, ColNames.WIND_SPEED, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("wind_speed", meta, units), + config=generate_chart_name(ColNames.WIND_SPEED, meta, units), figure=speed, ) @@ -415,7 +416,7 @@ 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, "wind_dir", global_local, si_ip) + direction = heatmap(df, ColNames.WIND_DIR, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( config=generate_chart_name("wind_direction", meta, units), @@ -452,13 +453,13 @@ def update_custom_wind_rose( # Wind Rose Graphs if start_month <= end_month: - df = df.loc[(df["month"] >= start_month) & (df["month"] <= end_month)] + df = df.loc[(df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month)] else: - df = df.loc[(df["month"] <= end_month) | (df["month"] >= start_month)] + df = df.loc[(df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month)] if start_hour <= end_hour: - df = df.loc[(df["hour"] >= start_hour) & (df["hour"] <= end_hour)] + df = df.loc[(df[ColNames.HOUR] >= start_hour) & (df[ColNames.HOUR] <= end_hour)] else: - df = df.loc[(df["hour"] <= end_hour) | (df["hour"] >= start_hour)] + 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 ) @@ -507,25 +508,25 @@ def update_seasonal_graphs(_, df, meta, si_ip): # Text winter_df = df.loc[ - (df["month"] <= winter_months[1]) | (df["month"] >= winter_months[0]) + (df[ColNames.MONTH] <= winter_months[1]) | (df[ColNames.MONTH] >= winter_months[0]) ] query_calm_wind = "wind_speed == 0" winter_total_count = winter_df.shape[0] winter_calm_count = winter_df.query(query_calm_wind).shape[0] spring_df = df.loc[ - (df["month"] >= spring_months[0]) & (df["month"] <= spring_months[1]) + (df[ColNames.MONTH] >= spring_months[0]) & (df[ColNames.MONTH] <= spring_months[1]) ] spring_total_count = spring_df.shape[0] spring_calm_count = spring_df.query(query_calm_wind).shape[0] summer_df = df.loc[ - (df["month"] >= summer_months[0]) & (df["month"] <= summer_months[1]) + (df[ColNames.MONTH] >= summer_months[0]) & (df[ColNames.MONTH] <= summer_months[1]) ] summer_total_count = summer_df.shape[0] summer_calm_count = summer_df.query(query_calm_wind).shape[0] - fall_df = df.loc[(df["month"] >= fall_months[0]) & (df["month"] <= fall_months[1])] + fall_df = df.loc[(df[ColNames.MONTH] >= fall_months[0]) & (df[ColNames.MONTH] <= fall_months[1])] fall_total_count = fall_df.shape[0] fall_calm_count = fall_df.query(query_calm_wind).shape[0] @@ -621,18 +622,18 @@ def update_daily_graphs(_, df, meta, si_ip): # Text query_calm_wind = "wind_speed == 0" morning_df = df.loc[ - (df["hour"] >= morning_times[0]) & (df["hour"] <= morning_times[1]) + (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1]) ] morning_total_count = morning_df.shape[0] morning_calm_count = morning_df.query(query_calm_wind).shape[0] noon_df = df.loc[ - (df["hour"] >= morning_times[0]) & (df["hour"] <= morning_times[1]) + (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1]) ] noon_total_count = noon_df.shape[0] noon_calm_count = noon_df.query(query_calm_wind).shape[0] - night_df = df.loc[(df["hour"] <= night_times[1]) | (df["hour"] >= night_times[0])] + night_df = df.loc[(df[ColNames.HOUR] <= night_times[1]) | (df[ColNames.HOUR] >= night_times[0])] night_total_count = night_df.shape[0] night_calm_count = night_df.query(query_calm_wind).shape[0] From 813821840edb526970161892af592c41a048454f Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sat, 23 Aug 2025 16:56:03 +1000 Subject: [PATCH 019/163] fix: replaced the string that using Class ColNames in charts_sun.py --- pages/lib/charts_sun.py | 45 +++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 73d43a24..103e11b4 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -15,14 +15,15 @@ ) from plotly.subplots import make_subplots from pvlib import solarposition +from pages.lib.global_column_names import ColNames def monthly_solar(epw_df, si_ip): g_h_rad_month_ave = ( - epw_df.groupby(["month", "hour"])["glob_hor_rad"].median().reset_index() + epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.GLOB_HOR_RAD].median().reset_index() ) dif_h_rad_month_ave = ( - epw_df.groupby(["month", "hour"])["dif_hor_rad"].median().reset_index() + epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.DIF_HOR_RAD].median().reset_index() ) fig = make_subplots( rows=1, @@ -37,9 +38,9 @@ def monthly_solar(epw_df, si_ip): fig.add_trace( go.Scatter( - x=g_h_rad_month_ave.loc[g_h_rad_month_ave["month"] == i + 1, "hour"], + x=g_h_rad_month_ave.loc[g_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR], y=g_h_rad_month_ave.loc[ - g_h_rad_month_ave["month"] == i + 1, "glob_hor_rad" + g_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.GLOB_HOR_RAD ], fill="tozeroy", mode="lines", @@ -47,12 +48,12 @@ def monthly_solar(epw_df, si_ip): line_width=2, name="Global", showlegend=is_first, - customdata=epw_df.loc[epw_df["month"] == i + 1, "month_names"], + customdata=epw_df.loc[epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES], hovertemplate=( "" + "Global Horizontal Solar Radiation" + ": %{y:.2f} " - + mapping_dictionary["glob_hor_rad"][si_ip]["unit"] + + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip]["unit"] + "
" + "Month: %{customdata}
" + "Hour: %{x}:00
" @@ -66,10 +67,10 @@ def monthly_solar(epw_df, si_ip): fig.add_trace( go.Scatter( x=dif_h_rad_month_ave.loc[ - dif_h_rad_month_ave["month"] == i + 1, "hour" + dif_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR ], y=dif_h_rad_month_ave.loc[ - dif_h_rad_month_ave["month"] == i + 1, "dif_hor_rad" + dif_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.DIF_HOR_RAD ], fill="tozeroy", mode="lines", @@ -77,12 +78,12 @@ def monthly_solar(epw_df, si_ip): line_width=2, name="Diffuse", showlegend=is_first, - customdata=epw_df.loc[epw_df["month"] == i + 1, "month_names"], + customdata=epw_df.loc[epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES], hovertemplate=( "" + "Diffuse Horizontal Solar Radiation" + ": %{y:.2f} " - + mapping_dictionary["dif_hor_rad"][si_ip]["unit"] + + mapping_dictionary[ColNames.DIF_HOR_RAD][si_ip]["unit"] + "
" + "Month: %{customdata}
" + "Hour: %{x}:00
" @@ -173,9 +174,9 @@ def polar_graph(df, meta, global_local, var, si_ip): marker_line_width=0, customdata=np.stack( ( - solpos["day"], - solpos["month_names"], - solpos["hour"], + solpos[ColNames.DAY], + solpos[ColNames.MONTH_NAMES], + solpos[ColNames.HOUR], solpos["elevation"], solpos["azimuth"], ), @@ -209,9 +210,9 @@ def polar_graph(df, meta, global_local, var, si_ip): ), customdata=np.stack( ( - solpos["day"], - solpos["month_names"], - solpos["hour"], + solpos[ColNames.DAY], + solpos[ColNames.MONTH_NAMES], + solpos[ColNames.HOUR], solpos["elevation"], solpos["azimuth"], solpos[var], @@ -352,9 +353,9 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): marker_line_width=0, customdata=np.stack( ( - df["day"], - df["month_names"], - df["hour"], + df[ColNames.DAY], + df[ColNames.MONTH_NAMES], + df[ColNames.HOUR], df["elevation"], df["azimuth"], ), @@ -388,9 +389,9 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): ), customdata=np.stack( ( - df["day"], - df["month_names"], - df["hour"], + df[ColNames.DAY], + df[ColNames.MONTH_NAMES], + df[ColNames.HOUR], df["elevation"], df["azimuth"], df[var], From 2ab0f0cae33d8ae514fe05cc5a14309025b16a5d Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Sat, 23 Aug 2025 17:12:03 +1000 Subject: [PATCH 020/163] fix: replaced the string that using Class ColNames in utils.py and psy-chart.py and summary.py --- pages/lib/utils.py | 11 ++++++----- pages/psy-chart.py | 33 +++++++++++++++++---------------- pages/summary.py | 17 +++++++++-------- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 860a0409..89038bae 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -8,6 +8,7 @@ from config import UnitSystem from pages.lib.global_scheme import fig_config, mapping_dictionary, month_lst +from pages.lib.global_column_names import ColNames def code_timer(func): @@ -215,13 +216,13 @@ def title_with_link( def summary_table_tmp_rh_tab(df, value, si_ip): df_summary = ( - df.groupby(["month_names", "month"])[value] + df.groupby([ColNames.MONTH_NAMES, ColNames.MONTH])[value] .describe(percentiles=[0.01, 0.25, 0.5, 0.75, 0.99]) .round(2) ) - df_summary = df_summary.reset_index(level="month_names").sort_index() + df_summary = df_summary.reset_index(level=ColNames.MONTH_NAMES).sort_index() df_summary = df_summary.drop(["count"], axis=1) - df_summary = df_summary.rename(columns={"month_names": "month"}) + df_summary = df_summary.rename(columns={ColNames.MONTH_NAMES: ColNames.MONTH}) df_sum = ( df[value] @@ -229,7 +230,7 @@ def summary_table_tmp_rh_tab(df, value, si_ip): .round(2) .to_frame() ) - df_sum = df_sum.T.assign(count="Year").rename(columns={"count": "month"}) + df_sum = df_sum.T.assign(count="Year").rename(columns={"count": ColNames.MONTH}) df_summary = pd.concat([df_summary, df_sum]) @@ -240,7 +241,7 @@ def summary_table_tmp_rh_tab(df, value, si_ip): ) return dash_table.DataTable( columns=[ - {"name": i, "id": i} if i == "month" else {"name": f"{i} ({unit})", "id": i} + {"name": i, "id": i} if i == ColNames.MONTH else {"name": f"{i} ({unit})", "id": i} for i in df_summary.columns ], style_table={"overflowX": "auto"}, diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 2c46b885..0836c4d1 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -12,6 +12,7 @@ from pythermalcomfort import psychrometrics as psy from config import PageUrls, DocLinks, PageInfo, UnitSystem +from pages.lib.global_column_names import ColNames from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -173,7 +174,7 @@ def inputs(): dropdown( id="psy-var-dropdown", options=dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -312,13 +313,13 @@ def update_psych_chart( if global_local == "global": # Set Global values for Max and minimum - var_range_x = mapping_dictionary["DBT"][si_ip]["range"] + var_range_x = mapping_dictionary[ColNames.DBT][si_ip]["range"] var_range_y = mapping_dictionary["hr"][si_ip]["range"] else: # Set maximum and minimum according to data - data_max = 5 * ceil(df["DBT"].max() / 5) - data_min = 5 * floor(df["DBT"].min() / 5) + data_max = 5 * ceil(df[ColNames.DBT].max() / 5) + data_min = 5 * floor(df[ColNames.DBT].min() / 5) var_range_x = [data_min, data_max] data_max = round(df["hr"].max(), 4) @@ -363,7 +364,7 @@ def update_psych_chart( showlegend=False, mode="lines", name="", - hovertemplate="RH " + str(rh) + "%", + hovertemplate=ColNames.RH + str(rh) + "%", line=dict(width=1, color="lightgrey"), ) ) @@ -374,7 +375,7 @@ def update_psych_chart( if var == "None": fig.add_trace( go.Scatter( - x=df["DBT"], + x=df[ColNames.DBT], y=df_hr_multiply, showlegend=False, mode="markers", @@ -384,16 +385,16 @@ def update_psych_chart( showscale=False, opacity=0.2, ), - hovertemplate=mapping_dictionary["DBT"]["name"] + hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + ": %{x:.2f}" - + mapping_dictionary["DBT"]["name"], + + mapping_dictionary[ColNames.DBT]["name"], name="", ) ) elif var == "Frequency": fig.add_trace( go.Histogram2d( - x=df["DBT"], + x=df[ColNames.DBT], y=df_hr_multiply, name="", colorscale=var_color, @@ -437,7 +438,7 @@ def update_psych_chart( fig.add_trace( go.Scatter( - x=df["DBT"], + x=df[ColNames.DBT], y=df_hr_multiply, showlegend=False, mode="markers", @@ -449,14 +450,14 @@ def update_psych_chart( colorscale=var_color, colorbar=var_colorbar, ), - customdata=np.stack((df["RH"], df["h"], df[var], df["t_dp"]), axis=-1), - hovertemplate=mapping_dictionary["DBT"]["name"] + customdata=np.stack((df[ColNames.RH], df["h"], df[var], df["t_dp"]), axis=-1), + hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + ": %{x:.2f}" - + mapping_dictionary["DBT"][si_ip]["unit"] + + mapping_dictionary[ColNames.DBT][si_ip]["unit"] + "
" - + mapping_dictionary["RH"]["name"] + + mapping_dictionary[ColNames.RH]["name"] + ": %{customdata[0]:.2f}" - + mapping_dictionary["RH"][si_ip]["unit"] + + mapping_dictionary[ColNames.RH][si_ip]["unit"] + "
" + mapping_dictionary["h"]["name"] + ": %{customdata[1]:.2f}" @@ -474,7 +475,7 @@ def update_psych_chart( ) ) - xtitle_name = "Temperature" + " " + mapping_dictionary["DBT"][si_ip]["unit"] + xtitle_name = "Temperature" + " " + mapping_dictionary[ColNames.DBT][si_ip]["unit"] ytitle_name = "Humidity Ratio" + " " + mapping_dictionary["hr"][si_ip]["unit"] fig.update_layout(template=template, margin=tight_margins) fig.update_xaxes( diff --git a/pages/summary.py b/pages/summary.py index c6611c11..20547b8d 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -11,6 +11,7 @@ from pages.lib.extract_df import get_data from pages.lib.global_scheme import template, tight_margins, mapping_dictionary from pages.lib.template_graphs import violin +from pages.lib.global_column_names import ColNames from pages.lib.utils import ( generate_chart_name, generate_units, @@ -262,7 +263,7 @@ def update_location_info(ts, df, meta, si_ip): total_solar_rad = f"Annual cumulative horizontal solar radiation: {total_solar_rad_value} {total_solar_rad_unit}" total_diffuse_rad = f"Percentage of diffuse horizontal solar radiation: {round(df['dif_hor_rad'].sum() / df['glob_hor_rad'].sum() * 100, 1)} %" - tmp_unit = mapping_dictionary["DBT"][si_ip]["unit"] + tmp_unit = mapping_dictionary[ColNames.DBT][si_ip]["unit"] average_yearly_tmp = ( f"Average yearly temperature: {df['DBT'].mean().round(1)} " + tmp_unit ) @@ -339,14 +340,14 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ hdd_array = [] cdd_array = [] - months = df["month_names"].unique() + 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)["DBT"].sub(hdd_setpoint) + a = df.query(query)[ColNames.DBT].sub(hdd_setpoint) hdd = a.sum(axis=0, skipna=True) hdd = hdd / 24 hdd = int(hdd) @@ -354,7 +355,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ # calculates CDD per month query = query_month + str(i) + " and DBT>=" + str(cdd_setpoint) - a = df.query(query)["DBT"].sub(cdd_setpoint) + a = df.query(query)[ColNames.DBT].sub(cdd_setpoint) cdd = a.sum(axis=0, skipna=True) cdd = cdd / 24 cdd = int(cdd) @@ -429,7 +430,7 @@ def update_violin_tdb(ts, global_local, df, meta, si_ip): id="tdb-profile-graph", className="violin-container", config=generate_chart_name("DryBulbTemperature", meta, units), - figure=violin(df, "DBT", global_local, si_ip), + figure=violin(df, ColNames.DBT, global_local, si_ip), ) @@ -452,7 +453,7 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): id="wind-profile-graph", className="violin-container", config=generate_chart_name("WindSpeed", meta, units), - figure=violin(df, "wind_speed", global_local, si_ip), + figure=violin(df, ColNames.WIND_SPEED, global_local, si_ip), ) @@ -475,7 +476,7 @@ def update_tab_rh(ts, global_local, df, meta, si_ip): id="rh-profile-graph", className="violin-container", config=generate_chart_name("RelativeHumidity", meta, units), - figure=violin(df, "RH", global_local, si_ip), + figure=violin(df, ColNames.RH, global_local, si_ip), ) @@ -498,7 +499,7 @@ def update_tab_gh_rad(ts, global_local, df, meta, si_ip): id="gh_rad-profile-graph", className="violin-container", config=generate_chart_name("GlobalHorizontalRadiation", meta, units), - figure=violin(df, "glob_hor_rad", global_local, si_ip), + figure=violin(df, ColNames.GLOB_HOR_RAD, global_local, si_ip), ) From be943a6844ee66c2f1a317a5e7a305375c5abd61 Mon Sep 17 00:00:00 2001 From: Kira-Liu00 Date: Sat, 23 Aug 2025 17:28:27 +1000 Subject: [PATCH 021/163] fix: Updated ElementIds of global_column_names.py --- pages/components.py | 31 +++++++++++++++++++++++++++++++ pages/lib/global_column_names.py | 27 ++++++++++++++++++++++++++- pages/outdoor.py | 1 + 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 pages/components.py diff --git a/pages/components.py b/pages/components.py new file mode 100644 index 00000000..1432930d --- /dev/null +++ b/pages/components.py @@ -0,0 +1,31 @@ + +class ElementIds: + DAILY = "daily" + DF_STORE = "df-store" + DROPDOWN = "dropdown" + HEATMAP = "heatmap" + GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + META_STORE = "meta-store" + SI_IP_UNIT_STORE = "si-ip-unit-store" + TABLE_TMP_HUM = "table-tmp-hum" + YEARLY_CHART = "yearly-chart" + + +class IdButtons: + DAILY_CHART_LABEL = "daily-chart-label" + + +class ComponentProperty: + CHILDREN = "children" + DATA = "data" + MODIFIED_TIMESTAMP = "modified_timestamp" + VALUE = "value" + + +class Text: + DAILY_CHART = "Daily chart" + YEARLY_CHART = "Yearly_chart" + HEATMAP_CHART = "Heatmap chart" + DESCRIPTIVE_STATISTICS = "Descriptive statistics" +class Type: + CIRCLE = "circle" diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index 35a3797e..a39bd60a 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -51,17 +51,41 @@ class ColNames(str, Enum): class ElementIds: # ==================== Defines the unique ID constant for each element in the front-end page ==================== + BEST_CONDITION_TEXT = "outdoor-comfort-output" DAILY = "daily" DF_STORE = "df-store" DROPDOWN = "dropdown" - HEATMAP = "heatmap" GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + HEATMAP = "heatmap" + HOUR_SLIDER = "outdoor-comfort-hour-slider" + IMAGE_SELECTION = "image-selection" + INVERT_HOUR = "invert-hour-outdoor-comfort" + INVERT_MONTH = "invert-month-outdoor-comfort" META_STORE = "meta-store" + MONTH_SLIDER = "outdoor-comfort-month-slider" + NORMALIZE_SWITCH = "outdoor-comfort-switches-input" + PSYCH_CHART = "psych-chart" + PSY_COLOR_BY_DROPDOWN = "psy-color-by-dropdown" + PSY_DATA_FILTER_BTN = "data-filter" + PSY_FILTER_VAR_DROPDOWN = "psy-var-dropdown" + PSY_HOUR_SLIDER = "psy-hour-slider" + PSY_INVERT_HOUR = "invert-hour-psy" + PSY_INVERT_MONTH = "invert-month-psy" + PSY_MAX_VAL_INPUT = "psy-max-val" + PSY_MIN_VAL_INPUT = "psy-min-val" + PSY_MONTH_SLIDER = "psy-month-slider" + PSY_TIME_FILTER_BTN = "month-hour-filter" + SCENARIO_DROPDOWN = "tab7-dropdown" SI_IP_UNIT_STORE = "si-ip-unit-store" TABLE_TMP_HUM = "table-tmp-hum" + TIME_FILTER_BTN = "month-hour-filter-outdoor-comfort" + UTCI_CATEGORY_HEATMAP = "utci-category-heatmap" + UTCI_HEATMAP = "utci-heatmap" + UTCI_SUMMARY_CHART = "utci-summary-chart" YEARLY_CHART = "yearly-chart" + class ComponentProperty: # ==================== Define common attribute name constants for components ==================== CHILDREN = "children" @@ -70,6 +94,7 @@ class ComponentProperty: VALUE = "value" + class Type: # ==================== Defines type constants available for UI components ==================== CIRCLE = "circle" diff --git a/pages/outdoor.py b/pages/outdoor.py index 59153930..72c57cc0 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -6,6 +6,7 @@ import numpy as np from config import PageUrls, DocLinks, PageInfo +from pages.lib.global_column_names import ElementIds, ComponentProperty, Type from pages.lib.global_scheme import ( outdoor_dropdown_names, ) From 2452ebe6f497ffdd96d34ca96aa92130dccab441 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Sat, 23 Aug 2025 17:45:05 +1000 Subject: [PATCH 022/163] fix: delete components.py --- pages/components.py | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 pages/components.py diff --git a/pages/components.py b/pages/components.py deleted file mode 100644 index 1432930d..00000000 --- a/pages/components.py +++ /dev/null @@ -1,31 +0,0 @@ - -class ElementIds: - DAILY = "daily" - DF_STORE = "df-store" - DROPDOWN = "dropdown" - HEATMAP = "heatmap" - GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - META_STORE = "meta-store" - SI_IP_UNIT_STORE = "si-ip-unit-store" - TABLE_TMP_HUM = "table-tmp-hum" - YEARLY_CHART = "yearly-chart" - - -class IdButtons: - DAILY_CHART_LABEL = "daily-chart-label" - - -class ComponentProperty: - CHILDREN = "children" - DATA = "data" - MODIFIED_TIMESTAMP = "modified_timestamp" - VALUE = "value" - - -class Text: - DAILY_CHART = "Daily chart" - YEARLY_CHART = "Yearly_chart" - HEATMAP_CHART = "Heatmap chart" - DESCRIPTIVE_STATISTICS = "Descriptive statistics" -class Type: - CIRCLE = "circle" From 33f4865e2401e28f9bc176f7f7c6bb0a033853c8 Mon Sep 17 00:00:00 2001 From: Ziqi Liu Date: Sat, 23 Aug 2025 18:06:40 +1000 Subject: [PATCH 023/163] Fix:Updated the ElementIDs of the explorer.py and global_column_names.py --- pages/explorer.py | 215 ++++++++++++++++--------------- pages/lib/global_column_names.py | 46 ++++++- 2 files changed, 153 insertions(+), 108 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index f2c899bd..20518b54 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -12,6 +12,7 @@ two_var_graph, three_var_graph, ) +from pages.lib.global_column_names import ElementIds from pages.lib.global_scheme import ( fig_config, dropdown_names, @@ -65,7 +66,7 @@ def section_one_inputs(): children=[ html.H4(className="text-next-to-input", children=["Select a variable: "]), dropdown( - id="sec1-var-dropdown", + id=ElementIds.SEC1_VAR_DROPDOWN, options=explore_dropdown_names, value="DBT", ), @@ -88,7 +89,7 @@ def section_one(): ), dcc.Loading( type="circle", - children=html.Div(id="yearly-explore", className="full-width"), + children=html.Div(id=ElementIds.YEARLY_EXPLORE, className="full-width"), ), html.Div( children=title_with_link( @@ -98,7 +99,7 @@ def section_one(): ), ), dcc.Loading( - html.Div(className="full-width", id="query-daily"), + html.Div(className="full-width", id=ElementIds.QUERY_DAILY), type="circle", ), html.Div( @@ -109,7 +110,7 @@ def section_one(): ), ), dcc.Loading( - html.Div(className="full-width", id="query-heatmap"), + html.Div(className="full-width", id=ElementIds.QUERY_HEATMAP), type="circle", ), html.Div( @@ -128,7 +129,7 @@ def section_one(): dbc.Button( "Apply month and hour filter", color="primary", - id="sec1-time-filter-input", + id=ElementIds.SEC1_TIME_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -140,7 +141,7 @@ def section_one(): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="sec1-month-slider", + id=ElementIds.SEC1_MONTH_SLIDER, min=1, max=12, step=1, @@ -159,7 +160,7 @@ def section_one(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-explore-descriptive", + id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, labelStyle={"flex": "30%"}, ), ], @@ -170,7 +171,7 @@ def section_one(): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="sec1-hour-slider", + id=ElementIds.SEC1_HOUR_SLIDER, min=0, max=24, step=1, @@ -189,7 +190,7 @@ def section_one(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-explore-descriptive", + id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, labelStyle={"flex": "30%"}, ), ], @@ -199,7 +200,7 @@ def section_one(): ], ), html.Div( - id="table-data-explorer", + id=ElementIds.TABLE_DATA_EXPLORER, ), ], ) @@ -230,7 +231,7 @@ def section_two_inputs(): style={"flex": "30%"}, ), dropdown( - id="sec2-var-dropdown", + id=ElementIds.SEC2_VAR_DROPDOWN, options=explore_dropdown_names, value="RH", style={"flex": "70%"}, @@ -245,7 +246,7 @@ def section_two_inputs(): dbc.Button( "Apply month and hour filter", color="primary", - id="sec2-time-filter-input", + id=ElementIds.SEC2_TIME_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -257,7 +258,7 @@ def section_two_inputs(): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="sec2-month-slider", + id=ElementIds.SEC2_MONTH_SLIDER, min=1, max=12, step=1, @@ -276,7 +277,7 @@ def section_two_inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-explore-heatmap", + id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, labelStyle={"flex": "30%"}, ), ], @@ -287,7 +288,7 @@ def section_two_inputs(): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="sec2-hour-slider", + id=ElementIds.SEC2_HOUR_SLIDER, min=0, max=24, step=1, @@ -306,7 +307,7 @@ def section_two_inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-explore-heatmap", + id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, labelStyle={"flex": "30%"}, ), ], @@ -319,7 +320,7 @@ def section_two_inputs(): dbc.Button( "Apply filter", color="primary", - id="sec2-data-filter-input", + id=ElementIds.SEC2_DATA_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -331,7 +332,7 @@ def section_two_inputs(): style={"flex": "30%"}, ), dropdown( - id="sec2-data-filter-var", + id=ElementIds.SEC2_DATA_FILTER_VAR, options=explore_dropdown_names, value="RH", style={"flex": "70%"}, @@ -345,7 +346,7 @@ def section_two_inputs(): children=["Min Value:"], style={"flex": "30%"} ), dbc.Input( - id="sec2-min-val", + id=ElementIds.SEC2_MIN_VAL, placeholder="Enter a number for the min val", type="number", value=0, @@ -361,7 +362,7 @@ def section_two_inputs(): children=["Max Value:"], style={"flex": "30%"} ), dbc.Input( - id="sec2-max-val", + id=ElementIds.SEC2_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=100, @@ -381,26 +382,28 @@ def section_two_inputs(): def section_two(): """Return the two graphs in section two.""" return html.Div( - id="tab6-sec2-container", + 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="custom-heatmap"), + children=html.Div(className="full-width", id=ElementIds.CUSTOM_HEATMAP), ), dbc.Checklist( options=[ {"label": "Normalize", "value": "normal"}, ], value=[], - id="normalize", + id=ElementIds.NORMALIZE, ), dcc.Loading( type="circle", children=[ dcc.Graph( - className="full-width", id="custom-summary", config=fig_config + className="full-width", + id=ElementIds.CUSTOM_SUMMARY, + config=fig_config, ), ], ), @@ -421,7 +424,7 @@ def section_three_inputs(): children=[ html.H6(style={"flex": "30%"}, children=["X Variable:"]), dropdown( - id="tab6-sec3-var-x-dropdown", + id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, options=explore_dropdown_names, value="DBT", style={"flex": "70%"}, @@ -433,7 +436,7 @@ def section_three_inputs(): children=[ html.H6(style={"flex": "30%"}, children=["Y Variable:"]), dropdown( - id="tab6-sec3-var-y-dropdown", + id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, options=explore_dropdown_names, value="RH", style={"flex": "70%"}, @@ -445,7 +448,7 @@ def section_three_inputs(): children=[ html.H6(style={"flex": "30%"}, children=["Color By:"]), dropdown( - id="tab6-sec3-colorby-dropdown", + id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, options=explore_dropdown_names, value="glob_hor_rad", style={"flex": "70%"}, @@ -460,7 +463,7 @@ def section_three_inputs(): dbc.Button( "Apply month and hour filter", color="primary", - id="tab6-sec3-time-filter-input", + id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -470,7 +473,7 @@ def section_three_inputs(): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="tab6-sec3-query-month-slider", + id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, min=1, max=12, step=1, @@ -489,7 +492,7 @@ def section_three_inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-explore-more-charts", + id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, labelStyle={"flex": "30%"}, ), ], @@ -500,7 +503,7 @@ def section_three_inputs(): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="tab6-sec3-query-hour-slider", + id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, min=0, max=24, step=1, @@ -519,7 +522,7 @@ def section_three_inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-explore-more-charts", + id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, labelStyle={"flex": "30%"}, ), ], @@ -532,7 +535,7 @@ def section_three_inputs(): dbc.Button( "Apply filter", color="primary", - id="tab6-sec3-data-filter-input", + id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, className="mb-2", n_clicks=0, ), @@ -543,7 +546,7 @@ def section_three_inputs(): children=["Filter Variable:"], style={"flex": "30%"} ), dropdown( - id="tab6-sec3-filter-var-dropdown", + id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, options=explore_dropdown_names, value="RH", style={"flex": "70%"}, @@ -556,7 +559,7 @@ def section_three_inputs(): html.H6(children=["Min Value:"], style={"flex": "30%"}), dbc.Input( className="num-input", - id="tab6-sec3-min-val", + id=ElementIds.TAB6_SEC3_MIN_VAL, placeholder="Enter a number for the min val", type="number", step=1, @@ -571,7 +574,7 @@ def section_three_inputs(): html.H6(children=["Max Value:"], style={"flex": "30%"}), dbc.Input( className="num-input", - id="tab6-sec3-max-val", + id=ElementIds.TAB6_SEC3_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=100, @@ -600,11 +603,11 @@ def section_three(): ), section_three_inputs(), dcc.Loading( - html.Div(id="three-var"), + html.Div(id=ElementIds.THREE_VAR), type="circle", ), dcc.Loading( - html.Div(id="two-var"), + html.Div(id=ElementIds.TWO_VAR), type="circle", ), ], @@ -620,17 +623,17 @@ def layout(): @callback( - Output("yearly-explore", "children"), + Output(ElementIds.YEARLY_EXPLORE, "children"), # Section One [ Input("df-store", "modified_timestamp"), - Input("sec1-var-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.META_STORE, "data"), + State(ElementIds.SI_IP_UNIT_STORE, "data"), ], ) def update_tab_yearly(_, var, global_local, df, meta, si_ip): @@ -653,16 +656,16 @@ def update_tab_yearly(_, var, global_local, df, meta, si_ip): @callback( - Output("query-daily", "children"), + Output(ElementIds.QUERY_DAILY, "children"), [ - Input("df-store", "modified_timestamp"), - Input("sec1-var-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.DF_STORE, "data"), + State(ElementIds.META_STORE, "data"), + State(ElementIds.SI_IP_UNIT_STORE, "data"), ], ) def update_tab_daily(_, var, global_local, df, meta, si_ip): @@ -678,16 +681,16 @@ def update_tab_daily(_, var, global_local, df, meta, si_ip): @callback( - Output("query-heatmap", "children"), + Output(ElementIds.QUERY_HEATMAP, "children"), [ - Input("df-store", "modified_timestamp"), - Input("sec1-var-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.META_STORE, "data"), + State(ElementIds.SI_IP_UNIT_STORE, "data"), ], ) def update_tab_heatmap(_, var, global_local, df, meta, si_ip): @@ -704,31 +707,31 @@ def update_tab_heatmap(_, var, global_local, df, meta, si_ip): @callback( [ - Output("custom-heatmap", "children"), - Output("custom-summary", "style"), - Output("custom-summary", "figure"), - Output("normalize", "style"), + Output(ElementIds.CUSTOM_HEATMAP, "children"), + Output(ElementIds.CUSTOM_SUMMARY, "style"), + Output(ElementIds.CUSTOM_SUMMARY, "figure"), + Output(ElementIds.NORMALIZE, "style"), ], [ - Input("df-store", "modified_timestamp"), - Input("sec2-var-dropdown", "value"), - Input("sec2-time-filter-input", "n_clicks"), - Input("sec2-data-filter-input", "n_clicks"), - Input("normalize", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.SEC2_VAR_DROPDOWN, "value"), + Input(ElementIds.SEC2_TIME_FILTER_INPUT, "n_clicks"), + Input(ElementIds.SEC2_DATA_FILTER_INPUT, "n_clicks"), + Input(ElementIds.NORMALIZE, "value"), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), ], # General [ - State("df-store", "data"), - State("sec2-month-slider", "value"), - State("sec2-hour-slider", "value"), - State("sec2-data-filter-var", "value"), - State("sec2-min-val", "value"), - State("sec2-max-val", "value"), - State("meta-store", "data"), - State("invert-month-explore-heatmap", "value"), - State("invert-hour-explore-heatmap", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.SEC2_MONTH_SLIDER, "value"), + State(ElementIds.SEC2_HOUR_SLIDER, "value"), + State(ElementIds.SEC2_DATA_FILTER_VAR, "value"), + State(ElementIds.SEC2_MIN_VAL, "value"), + State(ElementIds.SEC2_MAX_VAL, "value"), + State(ElementIds.META_STORE, "data"), + State(ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, "value"), + State(ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, "value"), + State(ElementIds.SI_IP_UNIT_STORE, "data"), ], ) def update_heatmap( @@ -820,27 +823,27 @@ def update_heatmap( @callback( - [Output("three-var", "children"), Output("two-var", "children")], + [Output(ElementIds.THREE_VAR, "children"), Output(ElementIds.TWO_VAR, "children")], [ - Input("df-store", "modified_timestamp"), - Input("tab6-sec3-var-x-dropdown", "value"), - Input("tab6-sec3-var-y-dropdown", "value"), - Input("tab6-sec3-colorby-dropdown", "value"), - Input("tab6-sec3-time-filter-input", "n_clicks"), - Input("tab6-sec3-data-filter-input", "n_clicks"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, "value"), + Input(ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, "value"), + Input(ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, "value"), + Input(ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, "n_clicks"), + Input(ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, "n_clicks"), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("tab6-sec3-query-month-slider", "value"), - State("tab6-sec3-query-hour-slider", "value"), - State("tab6-sec3-filter-var-dropdown", "value"), - State("tab6-sec3-min-val", "value"), - State("tab6-sec3-max-val", "value"), - State("meta-store", "data"), - State("invert-month-explore-more-charts", "value"), - State("invert-hour-explore-more-charts", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, "value"), + State(ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, "value"), + State(ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, "value"), + State(ElementIds.TAB6_SEC3_MIN_VAL, "value"), + State(ElementIds.TAB6_SEC3_MAX_VAL, "value"), + State(ElementIds.META_STORE, "data"), + State(ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, "value"), + State(ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, "value"), + State(ElementIds.SI_IP_UNIT_STORE, "data"), ], ) def update_more_charts( @@ -912,19 +915,19 @@ def update_more_charts( @callback( - Output("table-data-explorer", "children"), + Output(ElementIds.TABLE_DATA_EXPLORER, "children"), [ - Input("df-store", "modified_timestamp"), - Input("sec1-var-dropdown", "value"), - Input("sec1-time-filter-input", "n_clicks"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), + Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), + Input(ElementIds.SEC1_TIME_FILTER_INPUT, "n_clicks"), ], [ - State("df-store", "data"), - State("si-ip-unit-store", "data"), - State("sec1-month-slider", "value"), - State("sec1-hour-slider", "value"), - State("invert-month-explore-descriptive", "value"), - State("invert-hour-explore-descriptive", "value"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.SI_IP_UNIT_STORE, "data"), + State(ElementIds.SEC1_MONTH_SLIDER, "value"), + State(ElementIds.SEC1_HOUR_SLIDER, "value"), + State(ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, "value"), + State(ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, "value"), ], ) def update_table( diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index a39bd60a..c57c6686 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -54,13 +54,28 @@ class ElementIds: BEST_CONDITION_TEXT = "outdoor-comfort-output" DAILY = "daily" DF_STORE = "df-store" + ID_EXPLORER_DF_STORE = "df-store" DROPDOWN = "dropdown" + CUSTOM_SUMMARY = "custom-summary" + CUSTOM_HEATMAP = "custom-heatmap" GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" HEATMAP = "heatmap" + MAIN_NV_SECTION = "main-nv-section" + NORMALIZE = "normalize" + NV_HEATMAP_CHART = "nv-heatmap-chart" HOUR_SLIDER = "outdoor-comfort-hour-slider" IMAGE_SELECTION = "image-selection" INVERT_HOUR = "invert-hour-outdoor-comfort" INVERT_MONTH = "invert-month-outdoor-comfort" + ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + INVERT_HOUR_EXPLORE_DESCRIPTIVE = "invert-hour-explore-descriptive" + INVERT_MONTH_EXPLORE_DESCRIPTIVE = "invert-month-explore-descriptive" + INVERT_MONTH_EXPLORE_HEATMAP = "invert-month-explore-heatmap" + INVERT_HOUR_EXPLORE_HEATMAP = "invert-hour-explore-heatmap" + INVERT_MONTH_EXPLORE_MORE_CHARTS = "invert-month-explore-more-charts" + INVERT_HOUR_EXPLORE_MORE_CHARTS = "invert-hour-explore-more-charts" + ID_EXPLORER_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_NATURAL_SI_IP_UNIT_STORE = "si-ip-unit-store" META_STORE = "meta-store" MONTH_SLIDER = "outdoor-comfort-month-slider" NORMALIZE_SWITCH = "outdoor-comfort-switches-input" @@ -75,15 +90,43 @@ class ElementIds: PSY_MIN_VAL_INPUT = "psy-min-val" PSY_MONTH_SLIDER = "psy-month-slider" PSY_TIME_FILTER_BTN = "month-hour-filter" + SEC1_HOUR_SLIDER = "sec1-hour-slider" + SEC1_MONTH_SLIDER = "sec1-month-slider" + SEC1_TIME_FILTER_INPUT = "sec1-time-filter-input" + SEC1_VAR_DROPDOWN = "sec1-var-dropdown" + SEC2_DATA_FILTER_INPUT = "sec2-data-filter-input" + SEC2_DATA_FILTER_VAR = "sec2-data-filter-var" + SEC2_VAR_DROPDOWN = "sec2-var-dropdown" + SEC2_HOUR_SLIDER = "sec2-hour-slider" + SEC2_TIME_FILTER_INPUT = "sec2-time-filter-input" + SEC2_MONTH_SLIDER = "sec2-month-slider" + SEC2_MIN_VAL = "sec2-min-val" + SEC2_MAX_VAL = "sec2-max-val" SCENARIO_DROPDOWN = "tab7-dropdown" SI_IP_UNIT_STORE = "si-ip-unit-store" TABLE_TMP_HUM = "table-tmp-hum" + TABLE_DATA_EXPLORER = "table-data-explorer" + TAB6_SEC2_CONTAINER = "tab6-sec2-container" + TAB6_SEC3_DATA_FILTER_INPUT = "tab6-sec3-data-filter-input" + TAB6_SEC3_FILTER_VAR_DROPDOWN = "tab6-sec3-filter-var-dropdown" + TAB6_SEC3_MIN_VAL = "tab6-sec3-min-val" + TAB6_SEC3_MAX_VAL = "tab6-sec3-max-val" + TAB6_SEC3_TIME_FILTER_INPUT = "tab6-sec3-time-filter-input" + TAB6_SEC3_QUERY_HOUR_SLIDER = "tab6-sec3-query-hour-slider" + TAB6_SEC3_QUERY_MONTH_SLIDER = "tab6-sec3-query-month-slider" + TAB6_SEC3_VAR_X_DROPDOWN = "tab6-sec3-var-x-dropdown" + TAB6_SEC3_VAR_Y_DROPDOWN = "tab6-sec3-var-y-dropdown" + TAB6_SEC3_COLORBY_DROPDOWN = "tab6-sec3-colorby-dropdown" TIME_FILTER_BTN = "month-hour-filter-outdoor-comfort" + TWO_VAR = "two-var" + THREE_VAR = "three-var" + QUERY_DAILY = "query-daily" + QUERY_HEATMAP = "query-heatmap" UTCI_CATEGORY_HEATMAP = "utci-category-heatmap" UTCI_HEATMAP = "utci-heatmap" UTCI_SUMMARY_CHART = "utci-summary-chart" YEARLY_CHART = "yearly-chart" - + YEARLY_EXPLORE = "yearly-explore" class ComponentProperty: @@ -94,7 +137,6 @@ class ComponentProperty: VALUE = "value" - class Type: # ==================== Defines type constants available for UI components ==================== CIRCLE = "circle" From cf56585c20b4517bfcbe3e219af5cfb6f3a375b0 Mon Sep 17 00:00:00 2001 From: Kira-Liu00 Date: Sat, 23 Aug 2025 18:18:44 +1000 Subject: [PATCH 024/163] fix: Updated ElementIds of the psy-chart and global _column _names.py --- pages/lib/global_column_names.py | 4 ++-- pages/psy-chart.py | 34 ++++++++++++++++---------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index a39bd60a..4289d43b 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -75,6 +75,7 @@ class ElementIds: PSY_MIN_VAL_INPUT = "psy-min-val" PSY_MONTH_SLIDER = "psy-month-slider" PSY_TIME_FILTER_BTN = "month-hour-filter" + PSY_CHART_BTN = "psy-chart-btn" SCENARIO_DROPDOWN = "tab7-dropdown" SI_IP_UNIT_STORE = "si-ip-unit-store" TABLE_TMP_HUM = "table-tmp-hum" @@ -83,8 +84,7 @@ class ElementIds: UTCI_HEATMAP = "utci-heatmap" UTCI_SUMMARY_CHART = "utci-summary-chart" YEARLY_CHART = "yearly-chart" - - + class ComponentProperty: # ==================== Define common attribute name constants for components ==================== diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 0836c4d1..61b85a58 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -12,7 +12,7 @@ from pythermalcomfort import psychrometrics as psy from config import PageUrls, DocLinks, PageInfo, UnitSystem -from pages.lib.global_column_names import ColNames +from pages.lib.global_column_names import ElementIds, ComponentProperty, Type from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -174,7 +174,7 @@ def inputs(): dropdown( id="psy-var-dropdown", options=dropdown_names, - value=ColNames.RH, + value="RH", style={"flex": "70%"}, ), ], @@ -313,13 +313,13 @@ def update_psych_chart( if global_local == "global": # Set Global values for Max and minimum - var_range_x = mapping_dictionary[ColNames.DBT][si_ip]["range"] + var_range_x = mapping_dictionary["DBT"][si_ip]["range"] var_range_y = mapping_dictionary["hr"][si_ip]["range"] else: # Set maximum and minimum according to data - data_max = 5 * ceil(df[ColNames.DBT].max() / 5) - data_min = 5 * floor(df[ColNames.DBT].min() / 5) + data_max = 5 * ceil(df["DBT"].max() / 5) + data_min = 5 * floor(df["DBT"].min() / 5) var_range_x = [data_min, data_max] data_max = round(df["hr"].max(), 4) @@ -364,7 +364,7 @@ def update_psych_chart( showlegend=False, mode="lines", name="", - hovertemplate=ColNames.RH + str(rh) + "%", + hovertemplate="RH " + str(rh) + "%", line=dict(width=1, color="lightgrey"), ) ) @@ -375,7 +375,7 @@ def update_psych_chart( if var == "None": fig.add_trace( go.Scatter( - x=df[ColNames.DBT], + x=df["DBT"], y=df_hr_multiply, showlegend=False, mode="markers", @@ -385,16 +385,16 @@ def update_psych_chart( showscale=False, opacity=0.2, ), - hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + hovertemplate=mapping_dictionary["DBT"]["name"] + ": %{x:.2f}" - + mapping_dictionary[ColNames.DBT]["name"], + + mapping_dictionary["DBT"]["name"], name="", ) ) elif var == "Frequency": fig.add_trace( go.Histogram2d( - x=df[ColNames.DBT], + x=df["DBT"], y=df_hr_multiply, name="", colorscale=var_color, @@ -438,7 +438,7 @@ def update_psych_chart( fig.add_trace( go.Scatter( - x=df[ColNames.DBT], + x=df["DBT"], y=df_hr_multiply, showlegend=False, mode="markers", @@ -450,14 +450,14 @@ def update_psych_chart( colorscale=var_color, colorbar=var_colorbar, ), - customdata=np.stack((df[ColNames.RH], df["h"], df[var], df["t_dp"]), axis=-1), - hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + customdata=np.stack((df["RH"], df["h"], df[var], df["t_dp"]), axis=-1), + hovertemplate=mapping_dictionary["DBT"]["name"] + ": %{x:.2f}" - + mapping_dictionary[ColNames.DBT][si_ip]["unit"] + + mapping_dictionary["DBT"][si_ip]["unit"] + "
" - + mapping_dictionary[ColNames.RH]["name"] + + mapping_dictionary["RH"]["name"] + ": %{customdata[0]:.2f}" - + mapping_dictionary[ColNames.RH][si_ip]["unit"] + + mapping_dictionary["RH"][si_ip]["unit"] + "
" + mapping_dictionary["h"]["name"] + ": %{customdata[1]:.2f}" @@ -475,7 +475,7 @@ def update_psych_chart( ) ) - xtitle_name = "Temperature" + " " + mapping_dictionary[ColNames.DBT][si_ip]["unit"] + xtitle_name = "Temperature" + " " + mapping_dictionary["DBT"][si_ip]["unit"] ytitle_name = "Humidity Ratio" + " " + mapping_dictionary["hr"][si_ip]["unit"] fig.update_layout(template=template, margin=tight_margins) fig.update_xaxes( From 2b82d7977cc99fa75b450fdf3f4d471de963415c Mon Sep 17 00:00:00 2001 From: Kira-Liu00 Date: Sat, 23 Aug 2025 18:36:55 +1000 Subject: [PATCH 025/163] Fix: Updated the ElementIds of global_column_names.py --- pages/lib/global_column_names.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index c57c6686..ba7e69dd 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -80,6 +80,7 @@ class ElementIds: MONTH_SLIDER = "outdoor-comfort-month-slider" NORMALIZE_SWITCH = "outdoor-comfort-switches-input" PSYCH_CHART = "psych-chart" + PSY_CHART_BTN = "psy-chart-btn" PSY_COLOR_BY_DROPDOWN = "psy-color-by-dropdown" PSY_DATA_FILTER_BTN = "data-filter" PSY_FILTER_VAR_DROPDOWN = "psy-var-dropdown" @@ -90,6 +91,7 @@ class ElementIds: PSY_MIN_VAL_INPUT = "psy-min-val" PSY_MONTH_SLIDER = "psy-month-slider" PSY_TIME_FILTER_BTN = "month-hour-filter" + SEC1_HOUR_SLIDER = "sec1-hour-slider" SEC1_MONTH_SLIDER = "sec1-month-slider" SEC1_TIME_FILTER_INPUT = "sec1-time-filter-input" From 34f1ad3c10534cc27a4951f0e581d2165004850d Mon Sep 17 00:00:00 2001 From: Ziqi Liu Date: Sat, 23 Aug 2025 19:02:53 +1000 Subject: [PATCH 026/163] Fix:Updated the ElementIDs of the explorer.py, global_column_names.py and natural_ventilation.py. --- pages/explorer.py | 32 ++++----- pages/lib/global_column_names.py | 17 +++++ pages/natural_ventilation.py | 115 +++++++++++++++++-------------- 3 files changed, 95 insertions(+), 69 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index 20518b54..5a5ed6e0 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -632,8 +632,8 @@ def layout(): ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), - State(ElementIds.META_STORE, "data"), - State(ElementIds.SI_IP_UNIT_STORE, "data"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_yearly(_, var, global_local, df, meta, si_ip): @@ -660,12 +660,12 @@ def update_tab_yearly(_, var, global_local, df, meta, si_ip): [ Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State(ElementIds.DF_STORE, "data"), - State(ElementIds.META_STORE, "data"), - State(ElementIds.SI_IP_UNIT_STORE, "data"), + State(ElementIds.ID_EXPLORER_DF_STORE, "data"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_daily(_, var, global_local, df, meta, si_ip): @@ -685,12 +685,12 @@ def update_tab_daily(_, var, global_local, df, meta, si_ip): [ Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_NATURAL_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), - State(ElementIds.META_STORE, "data"), - State(ElementIds.SI_IP_UNIT_STORE, "data"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_heatmap(_, var, global_local, df, meta, si_ip): @@ -718,7 +718,7 @@ def update_tab_heatmap(_, var, global_local, df, meta, si_ip): Input(ElementIds.SEC2_TIME_FILTER_INPUT, "n_clicks"), Input(ElementIds.SEC2_DATA_FILTER_INPUT, "n_clicks"), Input(ElementIds.NORMALIZE, "value"), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], # General [ @@ -728,10 +728,10 @@ def update_tab_heatmap(_, var, global_local, df, meta, si_ip): State(ElementIds.SEC2_DATA_FILTER_VAR, "value"), State(ElementIds.SEC2_MIN_VAL, "value"), State(ElementIds.SEC2_MAX_VAL, "value"), - State(ElementIds.META_STORE, "data"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), State(ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, "value"), State(ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, "value"), - State(ElementIds.SI_IP_UNIT_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_heatmap( @@ -831,7 +831,7 @@ def update_heatmap( Input(ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, "value"), Input(ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, "n_clicks"), Input(ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, "n_clicks"), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), @@ -840,10 +840,10 @@ def update_heatmap( State(ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, "value"), State(ElementIds.TAB6_SEC3_MIN_VAL, "value"), State(ElementIds.TAB6_SEC3_MAX_VAL, "value"), - State(ElementIds.META_STORE, "data"), + State(ElementIds.ID_EXPLORER_META_STORE, "data"), State(ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, "value"), State(ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, "value"), - State(ElementIds.SI_IP_UNIT_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_more_charts( @@ -923,7 +923,7 @@ def update_more_charts( ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), - State(ElementIds.SI_IP_UNIT_STORE, "data"), + State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), State(ElementIds.SEC1_MONTH_SLIDER, "value"), State(ElementIds.SEC1_HOUR_SLIDER, "value"), State(ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, "value"), diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index c57c6686..38ccf6d5 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -54,19 +54,33 @@ class ElementIds: BEST_CONDITION_TEXT = "outdoor-comfort-output" DAILY = "daily" DF_STORE = "df-store" + ENABLE_CONDENSATION = "enable-condensation" ID_EXPLORER_DF_STORE = "df-store" + ID_EXPLORER_META_STORE = "meta-store" + ID_NATURAL_DF_STORE = "df-store" + ID_NATURAL_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" DROPDOWN = "dropdown" CUSTOM_SUMMARY = "custom-summary" CUSTOM_HEATMAP = "custom-heatmap" GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" HEATMAP = "heatmap" MAIN_NV_SECTION = "main-nv-section" + ID_NATURAL_META_STORE = "meta-store" NORMALIZE = "normalize" + NV_BAR_CHART = "nv-bar-chart" + NV_DBT_FILTER = "nv-dbt-filter" + NV_DPT_FILTER = "nv-dpt-filter" + NV_DPT_MAX_VAL = "nv-dpt-max-val" + NV_TDB_MIN_VAL = "nv-tdb-min-val" + NV_TDB_MAX_VAL = "nv-tdb-max-val" NV_HEATMAP_CHART = "nv-heatmap-chart" HOUR_SLIDER = "outdoor-comfort-hour-slider" + NV_MONTH_HOUR_FILTER = "nv-month-hour-filter" + NV_MONTH_SLIDER = "nv-month-slider" IMAGE_SELECTION = "image-selection" INVERT_HOUR = "invert-hour-outdoor-comfort" INVERT_MONTH = "invert-month-outdoor-comfort" + INVERT_MONTH_NV = "invert-month-nv" ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" INVERT_HOUR_EXPLORE_DESCRIPTIVE = "invert-hour-explore-descriptive" INVERT_MONTH_EXPLORE_DESCRIPTIVE = "invert-month-explore-descriptive" @@ -74,11 +88,13 @@ class ElementIds: INVERT_HOUR_EXPLORE_HEATMAP = "invert-hour-explore-heatmap" INVERT_MONTH_EXPLORE_MORE_CHARTS = "invert-month-explore-more-charts" INVERT_HOUR_EXPLORE_MORE_CHARTS = "invert-hour-explore-more-charts" + INVERT_HOUR_NV = "invert-hour-nv" ID_EXPLORER_SI_IP_UNIT_STORE = "si-ip-unit-store" ID_NATURAL_SI_IP_UNIT_STORE = "si-ip-unit-store" META_STORE = "meta-store" MONTH_SLIDER = "outdoor-comfort-month-slider" NORMALIZE_SWITCH = "outdoor-comfort-switches-input" + NV_HOUR_SLIDER = "nv-hour-slider" PSYCH_CHART = "psych-chart" PSY_COLOR_BY_DROPDOWN = "psy-color-by-dropdown" PSY_DATA_FILTER_BTN = "data-filter" @@ -104,6 +120,7 @@ class ElementIds: SEC2_MAX_VAL = "sec2-max-val" SCENARIO_DROPDOWN = "tab7-dropdown" SI_IP_UNIT_STORE = "si-ip-unit-store" + SWITCHES_INPUT = "switches-input" TABLE_TMP_HUM = "table-tmp-hum" TABLE_DATA_EXPLORER = "table-data-explorer" TAB6_SEC2_CONTAINER = "tab6-sec2-container" diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 7dcfe8fa..b9dbac83 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,3 +1,4 @@ + import math import dash @@ -18,7 +19,7 @@ container_col_center_one_of_three, ) from pages.lib.template_graphs import filter_df_by_month_and_hour -from pages.lib.global_column_names import ColNames +from pages.lib.global_column_names import ColNames, ElementIds from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -41,14 +42,17 @@ def layout(): return html.Div( className="container-col", - id="main-nv-section", + id=ElementIds.MAIN_NV_SECTION, children=[ # ], ) -@callback(Output("main-nv-section", "children"), [Input("si-ip-radio-input", "value")]) +@callback( + Output(ElementIds.MAIN_NV_SECTION, "children"), + [Input(ElementIds.ID_NATURAL_SI_IP_UNIT_STORE, "value")], +) def update_layout(si_ip): if si_ip == UnitSystem.IP: tdb_set_min = 50 @@ -70,7 +74,7 @@ def update_layout(si_ip): inputs_tab(tdb_set_min, tdb_set_max, dpt_set), dcc.Loading( html.Div( - id="nv-heatmap-chart", + id=ElementIds.NV_HEATMAP_CHART, style={"marginTop": "1rem"}, ), type="circle", @@ -83,7 +87,7 @@ def update_layout(si_ip): {"label": "", "value": 1}, ], value=[1], - id="switches-input", + id=ElementIds.SWITCHES_INPUT, switch=True, style={ "padding": "1rem", @@ -105,7 +109,7 @@ def update_layout(si_ip): ), dcc.Loading( html.Div( - id="nv-bar-chart", + id=ElementIds.NV_BAR_CHART, style={"marginTop": "1rem"}, ), type="circle", @@ -123,7 +127,7 @@ def inputs_tab(t_min, t_max, d_set): dbc.Button( "Apply filter", color="primary", - id="nv-dbt-filter", + id=ElementIds.NV_DBT_FILTER, className="mb-2", n_clicks=1, ), @@ -133,7 +137,7 @@ def inputs_tab(t_min, t_max, d_set): children=[ html.H6(children=["Min Value:"], style={"flex": "30%"}), dbc.Input( - id="nv-tdb-min-val", + id=ElementIds.NV_TDB_MIN_VAL, placeholder="Enter a number for the min val", type="number", step=1, @@ -147,7 +151,7 @@ def inputs_tab(t_min, t_max, d_set): children=[ html.H6(children=["Max Value:"], style={"flex": "30%"}), dbc.Input( - id="nv-tdb-max-val", + id=ElementIds.NV_TDB_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=t_max, @@ -164,7 +168,7 @@ def inputs_tab(t_min, t_max, d_set): dbc.Button( "Apply month and hour filter", color="primary", - id="nv-month-hour-filter", + id=ElementIds.NV_MONTH_HOUR_FILTER, className="mb-2", n_clicks=0, ), @@ -174,7 +178,7 @@ def inputs_tab(t_min, t_max, d_set): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="nv-month-slider", + id=ElementIds.NV_MONTH_SLIDER, min=1, max=12, step=1, @@ -193,7 +197,7 @@ def inputs_tab(t_min, t_max, d_set): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-nv", + id=ElementIds.INVERT_MONTH_NV, labelStyle={"flex": "30%"}, ), ], @@ -204,7 +208,7 @@ def inputs_tab(t_min, t_max, d_set): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="nv-hour-slider", + id=ElementIds.NV_HOUR_SLIDER, min=0, max=24, step=1, @@ -223,7 +227,7 @@ def inputs_tab(t_min, t_max, d_set): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-nv", + id=ElementIds.INVERT_HOUR_NV, labelStyle={"flex": "30%"}, ), ], @@ -236,7 +240,7 @@ def inputs_tab(t_min, t_max, d_set): dbc.Button( "Apply filter", color="primary", - id="nv-dpt-filter", + id=ElementIds.NV_DPT_FILTER, className="mb-2", n_clicks=0, disabled=True, @@ -254,7 +258,7 @@ def inputs_tab(t_min, t_max, d_set): }, ], value=[], - id="enable-condensation", + id=ElementIds.ENABLE_CONDENSATION, ), html.Div( className=container_row_center_full, @@ -264,7 +268,7 @@ def inputs_tab(t_min, t_max, d_set): style={"marginRight": "1rem"}, ), dbc.Input( - id="nv-dpt-max-val", + id=ElementIds.NV_DPT_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=d_set, @@ -280,26 +284,26 @@ def inputs_tab(t_min, t_max, d_set): @callback( - Output("nv-heatmap-chart", "children"), + Output(ElementIds.NV_HEATMAP_CHART, "children"), [ - Input("df-store", "modified_timestamp"), - Input("nv-month-hour-filter", "n_clicks"), - Input("nv-dbt-filter", "n_clicks"), - Input("nv-dpt-filter", "n_clicks"), - Input("global-local-radio-input", "value"), - Input("enable-condensation", "value"), + Input(ElementIds.ID_NATURAL_DF_STORE, "modified_timestamp"), + Input(ElementIds.NV_MONTH_HOUR_FILTER, "n_clicks"), + Input(ElementIds.NV_DBT_FILTER, "n_clicks"), + Input(ElementIds.NV_DPT_FILTER, "n_clicks"), + Input(ElementIds.ID_NATURAL_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ENABLE_CONDENSATION, "value"), ], [ - State("df-store", "data"), - State("nv-month-slider", "value"), - State("nv-hour-slider", "value"), - State("nv-tdb-min-val", "value"), - State("nv-tdb-max-val", "value"), - State("nv-dpt-max-val", "value"), - State("meta-store", "data"), - State("invert-month-nv", "value"), - State("invert-hour-nv", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_NATURAL_DF_STORE, "data"), + State(ElementIds.NV_MONTH_SLIDER, "value"), + State(ElementIds.NV_HOUR_SLIDER, "value"), + State(ElementIds.NV_TDB_MIN_VAL, "value"), + State(ElementIds.NV_TDB_MAX_VAL, "value"), + State(ElementIds.NV_DPT_MAX_VAL, "value"), + State(ElementIds.ID_NATURAL_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_NV, "value"), + State(ElementIds.INVERT_HOUR_NV, "value"), + State(ElementIds.ID_NATURAL_SI_IP_UNIT_STORE, "data"), ], ) def nv_heatmap( @@ -447,26 +451,26 @@ def nv_heatmap( @callback( - Output("nv-bar-chart", "children"), + Output(ElementIds.NV_BAR_CHART, "children"), [ - Input("df-store", "modified_timestamp"), - Input("nv-month-hour-filter", "n_clicks"), - Input("nv-dbt-filter", "n_clicks"), - Input("nv-dpt-filter", "n_clicks"), - Input("switches-input", "value"), - Input("enable-condensation", "value"), + Input(ElementIds.ID_NATURAL_DF_STORE, "modified_timestamp"), + 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.ENABLE_CONDENSATION, "value"), ], [ - State("df-store", "data"), - State("nv-month-slider", "value"), - State("nv-hour-slider", "value"), - State("nv-tdb-min-val", "value"), - State("nv-tdb-max-val", "value"), - State("nv-dpt-max-val", "value"), - State("meta-store", "data"), - State("invert-month-nv", "value"), - State("invert-hour-nv", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_NATURAL_DF_STORE, "data"), + State(ElementIds.NV_MONTH_SLIDER, "value"), + State(ElementIds.NV_HOUR_SLIDER, "value"), + State(ElementIds.NV_TDB_MIN_VAL, "value"), + State(ElementIds.NV_TDB_MAX_VAL, "value"), + State(ElementIds.NV_DPT_MAX_VAL, "value"), + State(ElementIds.ID_NATURAL_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_NV, "value"), + State(ElementIds.INVERT_HOUR_NV, "value"), + State(ElementIds.ID_NATURAL_SI_IP_UNIT_STORE, "data"), ], ) def nv_bar_chart( @@ -513,7 +517,9 @@ def nv_bar_chart( ) # this should be the total after filtering by time - tot_month_hours = df.groupby(df[ColNames.UTC_TIME].dt.month)["nv_allowed"].sum().values + tot_month_hours = ( + df.groupby(df[ColNames.UTC_TIME].dt.month)["nv_allowed"].sum().values + ) if dbt_data_filter and (min_dbt_val <= max_dbt_val): df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), "nv_allowed"] = 0 @@ -610,7 +616,10 @@ def nv_bar_chart( ) -@callback(Output("nv-dpt-filter", "disabled"), Input("enable-condensation", "value")) +@callback( + Output(ElementIds.NV_DPT_FILTER, "disabled"), + Input(ElementIds.ENABLE_CONDENSATION, "value"), +) def enable_disable_button_data_filter(state_checklist): if len(state_checklist) == 1: return False From 85d45a03de7d7a6a4f89e92cc65635a5746887b8 Mon Sep 17 00:00:00 2001 From: Kira-Liu00 Date: Sun, 24 Aug 2025 17:37:10 +1000 Subject: [PATCH 027/163] fix: Updated the ElementIds of outdoor.py, psy-chart.py, global_column_names.py and natural_ventilation.py --- pages/lib/global_column_names.py | 43 ++++++++----- pages/natural_ventilation.py | 2 +- pages/outdoor.py | 104 +++++++++++++++---------------- pages/psy-chart.py | 54 ++++++++-------- 4 files changed, 107 insertions(+), 96 deletions(-) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index 31b21731..848da917 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -51,7 +51,7 @@ class ColNames(str, Enum): class ElementIds: # ==================== Defines the unique ID constant for each element in the front-end page ==================== - BEST_CONDITION_TEXT = "outdoor-comfort-output" + OUTDOOR_COMFORT_OUTPUT = "outdoor-comfort-output" DAILY = "daily" DF_STORE = "df-store" ENABLE_CONDENSATION = "enable-condensation" @@ -74,12 +74,12 @@ class ElementIds: NV_TDB_MIN_VAL = "nv-tdb-min-val" NV_TDB_MAX_VAL = "nv-tdb-max-val" NV_HEATMAP_CHART = "nv-heatmap-chart" - HOUR_SLIDER = "outdoor-comfort-hour-slider" + OUTDOOR_COMFORT_HOUR_SLIDER = "outdoor-comfort-hour-slider" NV_MONTH_HOUR_FILTER = "nv-month-hour-filter" NV_MONTH_SLIDER = "nv-month-slider" IMAGE_SELECTION = "image-selection" - INVERT_HOUR = "invert-hour-outdoor-comfort" - INVERT_MONTH = "invert-month-outdoor-comfort" + INVERT_HOUR_OUTDOOR_COMFORT = "invert-hour-outdoor-comfort" + INVERT_MONTH_OUTDOOR_COMFORT = "invert-month-outdoor-comfort" INVERT_MONTH_NV = "invert-month-nv" ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" INVERT_HOUR_EXPLORE_DESCRIPTIVE = "invert-hour-explore-descriptive" @@ -91,27 +91,31 @@ class ElementIds: INVERT_HOUR_NV = "invert-hour-nv" ID_EXPLORER_SI_IP_UNIT_STORE = "si-ip-unit-store" ID_NATURAL_SI_IP_UNIT_STORE = "si-ip-unit-store" + SI_IP_RADIO_INPUT = "si-ip-radio-input" + META_STORE = "meta-store" - MONTH_SLIDER = "outdoor-comfort-month-slider" - NORMALIZE_SWITCH = "outdoor-comfort-switches-input" + OUTDOOR_COMFORT_MONTH_SLIDER = "outdoor-comfort-month-slider" + OUTDOOR_COMFORT_SWITCHES_INPUT = "outdoor-comfort-switches-input" NV_HOUR_SLIDER = "nv-hour-slider" PSYCH_CHART = "psych-chart" PSY_CHART_BTN = "psy-chart-btn" PSY_COLOR_BY_DROPDOWN = "psy-color-by-dropdown" - PSY_DATA_FILTER_BTN = "data-filter" - PSY_FILTER_VAR_DROPDOWN = "psy-var-dropdown" + DATA_FILTER = "data-filter" + PSY_VAR_DROPDOWN = "psy-var-dropdown" PSY_HOUR_SLIDER = "psy-hour-slider" - PSY_INVERT_HOUR = "invert-hour-psy" - PSY_INVERT_MONTH = "invert-month-psy" - PSY_MAX_VAL_INPUT = "psy-max-val" - PSY_MIN_VAL_INPUT = "psy-min-val" + INVERT_HOUR_PSY = "invert-hour-psy" + INVERT_MONTH_PSY = "invert-month-psy" + PSY_MAX_VAL = "psy-max-val" + PSY_MIN_VAL = "psy-min-val" PSY_MONTH_SLIDER = "psy-month-slider" - PSY_TIME_FILTER_BTN = "month-hour-filter" + ID_OUTDOOR_MONTH_HOUR_FILTER = "month-hour-filter" + MONTH_HOUR_FILTER = "month-hour-filter" SEC1_HOUR_SLIDER = "sec1-hour-slider" SEC1_MONTH_SLIDER = "sec1-month-slider" SEC1_TIME_FILTER_INPUT = "sec1-time-filter-input" SEC1_VAR_DROPDOWN = "sec1-var-dropdown" + SDATA_FILTER = "data-filter" SEC2_DATA_FILTER_INPUT = "sec2-data-filter-input" SEC2_DATA_FILTER_VAR = "sec2-data-filter-var" SEC2_VAR_DROPDOWN = "sec2-var-dropdown" @@ -120,7 +124,7 @@ class ElementIds: SEC2_MONTH_SLIDER = "sec2-month-slider" SEC2_MIN_VAL = "sec2-min-val" SEC2_MAX_VAL = "sec2-max-val" - SCENARIO_DROPDOWN = "tab7-dropdown" + TAB7_DROPDOWN = "tab7-dropdown" SI_IP_UNIT_STORE = "si-ip-unit-store" SWITCHES_INPUT = "switches-input" TABLE_TMP_HUM = "table-tmp-hum" @@ -136,7 +140,7 @@ class ElementIds: TAB6_SEC3_VAR_X_DROPDOWN = "tab6-sec3-var-x-dropdown" TAB6_SEC3_VAR_Y_DROPDOWN = "tab6-sec3-var-y-dropdown" TAB6_SEC3_COLORBY_DROPDOWN = "tab6-sec3-colorby-dropdown" - TIME_FILTER_BTN = "month-hour-filter-outdoor-comfort" + MONTH_HOUR_FILTER_OUTDOOR_COMFORT = "month-hour-filter-outdoor-comfort" TWO_VAR = "two-var" THREE_VAR = "three-var" QUERY_DAILY = "query-daily" @@ -146,7 +150,14 @@ class ElementIds: UTCI_SUMMARY_CHART = "utci-summary-chart" YEARLY_CHART = "yearly-chart" YEARLY_EXPLORE = "yearly-explore" - + ID_OUTDOOR_DF_STORE = "df-store" + ID_OUTDOOR_META_STORE = "meta-store" + ID_OUTDOOR_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_PSY_CHART_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_PSY_CHART_DF_STORE = "df-store" + ID_PSY_CHART_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_PSY_CHART_META_STORE = "meta-store" class ComponentProperty: # ==================== Define common attribute name constants for components ==================== diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index b9dbac83..3c16861c 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -51,7 +51,7 @@ def layout(): @callback( Output(ElementIds.MAIN_NV_SECTION, "children"), - [Input(ElementIds.ID_NATURAL_SI_IP_UNIT_STORE, "value")], + [Input(ElementIds.SI_IP_RADIO_INPUT, "value")], ) def update_layout(si_ip): if si_ip == UnitSystem.IP: diff --git a/pages/outdoor.py b/pages/outdoor.py index 72c57cc0..d6a805be 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -48,12 +48,12 @@ def inputs_outdoor_comfort(): style={"flex": "30%"}, ), dropdown( - id="tab7-dropdown", + id=ElementIds.TAB7_DROPDOWN, style={"flex": "60%"}, options=outdoor_dropdown_names, value="utci_Sun_Wind", ), - html.Div(id="image-selection", style={"flex": "10%"}), + html.Div(id=ElementIds.IMAGE_SELECTION, style={"flex": "10%"}), ], ), ], @@ -68,7 +68,7 @@ def inputs_outdoor_comfort(): style={ "width": "100%", }, - id="month-hour-filter-outdoor-comfort", + id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, className="mb-2", n_clicks=0, ), @@ -78,7 +78,7 @@ def inputs_outdoor_comfort(): html.H6("Month Range", style={"flex": "5%"}), html.Div( dcc.RangeSlider( - id="outdoor-comfort-month-slider", + id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, min=1, max=12, step=1, @@ -97,7 +97,7 @@ def inputs_outdoor_comfort(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-outdoor-comfort", + id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, labelStyle={"flex": "30%"}, ), ], @@ -108,7 +108,7 @@ def inputs_outdoor_comfort(): html.H6("Hour Range", style={"flex": "5%"}), html.Div( dcc.RangeSlider( - id="outdoor-comfort-hour-slider", + id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, min=0, max=24, step=1, @@ -127,7 +127,7 @@ def inputs_outdoor_comfort(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-outdoor-comfort", + id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, labelStyle={"flex": "30%"}, ), ], @@ -141,7 +141,7 @@ def inputs_outdoor_comfort(): def outdoor_comfort_chart(): return html.Div( children=[ - html.Div(id="outdoor-comfort-output"), + html.Div(id=ElementIds.OUTDOOR_COMFORT_OUTPUT), html.Div( children=title_with_link( text="UTCI heatmap chart", @@ -150,7 +150,7 @@ def outdoor_comfort_chart(): ) ), dcc.Loading( - html.Div(id="utci-heatmap"), + html.Div(id=ElementIds.UTCI_HEATMAP), type="circle", ), html.Div( @@ -161,7 +161,7 @@ def outdoor_comfort_chart(): ) ), dcc.Loading( - html.Div(id="utci-category-heatmap"), + html.Div(id=ElementIds.UTCI_CATEGORY_HEATMAP), type="circle", ), html.Div( @@ -172,7 +172,7 @@ def outdoor_comfort_chart(): {"label": "", "value": 1}, ], value=[1], - id="outdoor-comfort-switches-input", + id=ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, switch=True, style={ "padding": "1rem", @@ -193,7 +193,7 @@ def outdoor_comfort_chart(): ], ), dcc.Loading( - html.Div(id="utci-summary-chart"), + html.Div(id=ElementIds.UTCI_SUMMARY_CHART), type="circle", ), ], @@ -213,12 +213,12 @@ def layout(): @callback( - Output("outdoor-comfort-output", "children"), + Output(ElementIds.OUTDOOR_COMFORT_OUTPUT, "children"), [ - Input("df-store", "modified_timestamp"), + Input(ElementIds.ID_OUTDOOR_DF_STORE, "modified_timestamp"), ], [ - State("df-store", "data"), + State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), ], ) def update_outdoor_comfort_output(_, df): @@ -245,21 +245,21 @@ def update_outdoor_comfort_output(_, df): @callback( - Output("utci-heatmap", "children"), + Output(ElementIds.UTCI_HEATMAP, "children"), [ - Input("df-store", "modified_timestamp"), - Input("tab7-dropdown", "value"), - Input("global-local-radio-input", "value"), - Input("month-hour-filter-outdoor-comfort", "n_clicks"), + Input(ElementIds.ID_OUTDOOR_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB7_DROPDOWN, "value"), + Input(ElementIds.ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), - State("outdoor-comfort-month-slider", "value"), - State("outdoor-comfort-hour-slider", "value"), - State("invert-month-outdoor-comfort", "value"), - State("invert-hour-outdoor-comfort", "value"), + State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), + State(ElementIds.ID_OUTDOOR_META_STORE, "data"), + State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), + State(ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, "value"), + State(ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, "value"), + State(ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, "value"), + State(ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, "value"), ], ) def update_tab_utci_value( @@ -295,8 +295,8 @@ def update_tab_utci_value( @callback( - Output("image-selection", "children"), - Input("tab7-dropdown", "value"), + Output(ElementIds.IMAGE_SELECTION, "children"), + Input(ElementIds.TAB7_DROPDOWN, "value"), ) def change_image_based_on_selection(value): if value == "utci_Sun_Wind": @@ -312,21 +312,21 @@ def change_image_based_on_selection(value): @callback( - Output("utci-category-heatmap", "children"), + Output(ElementIds.UTCI_CATEGORY_HEATMAP, "children"), [ - Input("df-store", "modified_timestamp"), - Input("tab7-dropdown", "value"), - Input("global-local-radio-input", "value"), - Input("month-hour-filter-outdoor-comfort", "n_clicks"), + Input(ElementIds.ID_OUTDOOR_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB7_DROPDOWN, "value"), + Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), - State("outdoor-comfort-month-slider", "value"), - State("outdoor-comfort-hour-slider", "value"), - State("invert-month-outdoor-comfort", "value"), - State("invert-hour-outdoor-comfort", "value"), + State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), + State(ElementIds.ID_OUTDOOR_META_STORE, "data"), + State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), + State(ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, "value"), + State(ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, "value"), + State(ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, "value"), + State(ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, "value"), ], ) def update_tab_utci_category( @@ -382,20 +382,20 @@ def update_tab_utci_category( @callback( - Output("utci-summary-chart", "children"), + Output(ElementIds.UTCI_SUMMARY_CHART, "children"), [ - Input("tab7-dropdown", "value"), - Input("month-hour-filter-outdoor-comfort", "n_clicks"), - Input("outdoor-comfort-switches-input", "value"), + Input(ElementIds.TAB7_DROPDOWN, "value"), + Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), + Input(ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, "value"), ], [ - State("df-store", "data"), - State("outdoor-comfort-month-slider", "value"), - State("outdoor-comfort-hour-slider", "value"), - State("meta-store", "data"), - State("invert-month-outdoor-comfort", "value"), - State("invert-hour-outdoor-comfort", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), + State(ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, "value"), + State(ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, "value"), + State(ElementIds.ID_OUTDOOR_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, "value"), + State(ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, "value"), + State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_utci_summary_chart( diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 61b85a58..a0ba9d1d 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -72,7 +72,7 @@ def inputs(): style={"flex": "30%"}, ), dropdown( - id="psy-color-by-dropdown", + id=ElementIds.PSY_COLOR_BY_DROPDOWN, options=psy_dropdown_names, value="Frequency", style={"flex": "70%"}, @@ -89,7 +89,7 @@ def inputs(): dbc.Button( "Apply month and hour filter", color="primary", - id="month-hour-filter", + id=ElementIds.MONTH_HOUR_FILTER, className="mb-2", n_clicks=0, ), @@ -99,7 +99,7 @@ def inputs(): html.H6("Month Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="psy-month-slider", + id=ElementIds.PSY_MONTH_SLIDER, min=1, max=12, step=1, @@ -118,7 +118,7 @@ def inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-month-psy", + id=ElementIds.INVERT_MONTH_PSY, labelStyle={"flex": "30%"}, ), ], @@ -129,7 +129,7 @@ def inputs(): html.H6("Hour Range", style={"flex": "20%"}), html.Div( dcc.RangeSlider( - id="psy-hour-slider", + id=ElementIds.PSY_HOUR_SLIDER, min=0, max=24, step=1, @@ -148,7 +148,7 @@ def inputs(): {"label": "Invert", "value": "invert"}, ], value=[], - id="invert-hour-psy", + id=ElementIds.INVERT_HOUR_PSY, labelStyle={"flex": "30%"}, ), ], @@ -161,7 +161,7 @@ def inputs(): dbc.Button( "Apply filter", color="primary", - id="data-filter", + id=ElementIds.DATA_FILTER, className="mb-2", n_clicks=0, ), @@ -172,7 +172,7 @@ def inputs(): children=["Filter Variable:"], style={"flex": "30%"} ), dropdown( - id="psy-var-dropdown", + id=ElementIds.PSY_VAR_DROPDOWN , options=dropdown_names, value="RH", style={"flex": "70%"}, @@ -184,7 +184,7 @@ def inputs(): children=[ html.H6(children=["Min Value:"], style={"flex": "30%"}), dbc.Input( - id="psy-min-val", + id=ElementIds.PSY_MIN_VAL, placeholder="Enter a number for the min val", type="number", step=1, @@ -198,7 +198,7 @@ def inputs(): children=[ html.H6(children=["Max Value:"], style={"flex": "30%"}), dbc.Input( - id="psy-max-val", + id=ElementIds.PSY_MAX_VAL, placeholder="Enter a number for the max val", type="number", value=100, @@ -226,7 +226,7 @@ def layout(): type="circle", children=html.Div( className="container-col", - children=[inputs(), html.Div(id="psych-chart")], + children=[inputs(), html.Div(id="ElementIds.PSYCH_CHART")], ), ), ) @@ -234,25 +234,25 @@ def layout(): # psychrometric chart @callback( - Output("psych-chart", "children"), + Output(ElementIds.PSYCH_CHART, "children"), [ - Input("df-store", "modified_timestamp"), - Input("psy-color-by-dropdown", "value"), - Input("month-hour-filter", "n_clicks"), - Input("data-filter", "n_clicks"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_PSY_CHART_DF_STORE, "modified_timestamp"), + Input(ElementIds.PSY_COLOR_BY_DROPDOWN, "value"), + Input(ElementIds.MONTH_HOUR_FILTER, "n_clicks"), + Input(ElementIds.DATA_FILTER, "n_clicks"), + Input(ElementIds.ID_PSY_CHART_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("psy-month-slider", "value"), - State("psy-hour-slider", "value"), - State("psy-min-val", "value"), - State("psy-max-val", "value"), - State("psy-var-dropdown", "value"), - State("meta-store", "data"), - State("invert-month-psy", "value"), - State("invert-hour-psy", "value"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_PSY_CHART_DF_STORE, "data"), + State(ElementIds.PSY_MONTH_SLIDER, "value"), + State(ElementIds.PSY_HOUR_SLIDER, "value"), + State(ElementIds.PSY_MIN_VAL, "value"), + State(ElementIds.PSY_MAX_VAL, "value"), + State(ElementIds.PSY_VAR_DROPDOWN , "value"), + State(ElementIds.ID_PSY_CHART_META_STORE, "data"), + State(ElementIds.INVERT_MONTH_PSY, "value"), + State(ElementIds.INVERT_HOUR_PSY, "value"), + State(ElementIds.ID_PSY_CHART_SI_IP_UNIT_STORE, "data"), ], ) def update_psych_chart( From 1e1cccfdfdd6362e9fe4ce490fb789c5f028ff32 Mon Sep 17 00:00:00 2001 From: FengW01 Date: Sun, 24 Aug 2025 17:52:21 +1000 Subject: [PATCH 028/163] Fix: update the sun.py, wind.py and global_column_names.py --- pages/lib/global_column_names.py | 48 ++++++++++ pages/psy-chart.py | 2 +- pages/sun.py | 79 ++++++++-------- pages/wind.py | 149 ++++++++++++++++--------------- 4 files changed, 164 insertions(+), 114 deletions(-) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index 848da917..c959918e 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -159,6 +159,54 @@ class ElementIds: ID_PSY_CHART_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" ID_PSY_CHART_META_STORE = "meta-store" + CUSTOM_SUN_VIEW_DROPDOWN = "custom-sun-view-dropdown" + CUSTOM_SUN_VAR_DROPDOWN = "custom-sun-var-dropdown" + CUSTOM_SUNPATH = "custom-sunpath" + TAB_EXPLORE_DROPDOWN = "tab4-explore-dropdown" + TAB4_DAILY = "tab4-daily" + TAB4_HEATMAP = "tab4-heatmap" + STATIC_SECTION = "static-section" + TAB_FOUR_CONTAINER = "tab-four-container" + MONTHLY_SOLAR = "monthly-solar" + CLOUD_COVER = "cloud-cover" + ID_SUN_SI_IP_RADIO_INPUT = "si-ip-radio-input" + ID_SUN_DF_STORE = "df-store" + ID_SUN_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_SUN_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_SUN_META_STORE = "meta-store" + SLIDER_CONTAINER = "slider-container" + MONTH_SLIDER = "month-slider" + HOUR_SLIDER = "hour-slider" + WINTER_WIND_ROSE = "winter-wind-rose" + WINTER_WIND_ROSE_TEXT = "winter-wind-rose-text" + SPRING_WIND_ROSE = "spring-wind-rose" + SPRING_WIND_ROSE_TEXT = "spring-wind-rose-text" + SUMMER_WIND_ROSE = "summer-wind-rose" + SUMMER_WIND_ROSE_TEXT = "summer-wind-rose-text" + FALL_WIND_ROSE = "fall-wind-rose" + FALL_WIND_ROSE_TEXT = "fall-wind-rose-text" + TAB5_DAILY_CONTAINER = "tab5-daily-container" + DAILY_WIND_ROSE_OUTER_CONTAINER = "daily-wind-rose-outer-container" + MORNING_WIND_ROSE = "morning-wind-rose" + MORNING_WIND_ROSE_TEXT = "morning-wind-rose-text" + NOON_WIND_ROSE = "noon-wind-rose" + NOON_WIND_ROSE_TEXT = "noon-wind-rose-text" + NIGHT_WIND_ROSE = "night-wind-rose" + NIGHT_WIND_ROSE_TEXT = "night-wind-rose-text" + TAB5_CUSTOM_DROPDOWN_CONTAINER = "tab5-custom-dropdown-container" + TAB5_CUSTOM_START_MONTH = "tab5-custom-start-month" + TAB5_CUSTOM_START_HOUR = "tab5-custom-start-hour" + TAB5_CUSTOM_END_MONTH = "tab5-custom-end-month" + TAB5_CUSTOM_END_HOUR = "tab5-custom-end-hour" + CUSTOM_WIND_ROSE = "custom-wind-rose" + WIND_ROSE = "wind-rose" + WIND_SPEED = "wind-speed" + WIND_DIRECTION = "wind-direction" + ID_WIND_DF_STORE = "df-store" + ID_WIND_META_STORE = "meta-store" + ID_WIND_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_WINDGLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + class ComponentProperty: # ==================== Define common attribute name constants for components ==================== CHILDREN = "children" diff --git a/pages/psy-chart.py b/pages/psy-chart.py index a0ba9d1d..83ba076f 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -226,7 +226,7 @@ def layout(): type="circle", children=html.Div( className="container-col", - children=[inputs(), html.Div(id="ElementIds.PSYCH_CHART")], + children=[inputs(), html.Div(id=ElementIds.PSYCH_CHART)], ), ), ) diff --git a/pages/sun.py b/pages/sun.py index a77c4c7f..7d35fae8 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -1,4 +1,5 @@ from copy import deepcopy +from pages.lib.global_column_names import ElementIds import dash import dash_bootstrap_components as dbc @@ -71,7 +72,7 @@ def sun_path(): style={"width": "10rem"}, ), dropdown( - id="custom-sun-view-dropdown", + id= ElementIds.CUSTOM_SUN_VIEW_DROPDOWN, options={ "Spherical": "polar", "Cartesian": "cartesian", @@ -91,7 +92,7 @@ def sun_path(): style={"width": "10rem"}, ), dropdown( - id="custom-sun-var-dropdown", + id= ElementIds.CUSTOM_SUN_VAR_DROPDOWN, options=sc_dropdown_names, value="None", style={"width": "20rem"}, @@ -101,7 +102,7 @@ def sun_path(): dcc.Loading( type="circle", children=html.Div( - id="custom-sunpath", + id= ElementIds.CUSTOM_SUNPATH, ), ), ], @@ -129,17 +130,17 @@ def explore_daily_heatmap(): style={"width": "10rem"}, ), dropdown( - id="tab4-explore-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="tab4-daily")), + dcc.Loading(type="circle", children=html.Div(id= ElementIds.TAB4_DAILY)), dcc.Loading( type="circle", - children=html.Div(id="tab4-heatmap"), + children=html.Div(id=ElementIds.TAB4_HEATMAP), ), ], ) @@ -147,7 +148,7 @@ def explore_daily_heatmap(): def static_section(): return html.Div( - id="static-section", + id= ElementIds.STATIC_SECTION, className="container-col full-width", children=[ # ... @@ -159,12 +160,12 @@ def layout(): """Contents of tab four.""" return html.Div( className="container-col", - id="tab-four-container", + id= ElementIds.TAB_FOUR_CONTAINER, children=[sun_path(), static_section(), explore_daily_heatmap()], ) -@callback(Output("static-section", "children"), [Input("si-ip-radio-input", "value")]) +@callback(Output(ElementIds.STATIC_SECTION, "children"), [Input(ElementIds.ID_SUN_SI_IP_RADIO_INPUT, "value")]) def update_static_section(si_ip): hor_unit = "Wh/m²" if si_ip == UnitSystem.IP: @@ -179,7 +180,7 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id="monthly-solar"), + children=html.Div(id= ElementIds.MONTHLY_SOLAR), ), html.Div( children=title_with_link( @@ -190,23 +191,23 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id="cloud-cover"), + children=html.Div(id= ElementIds.CLOUD_COVER), ), ] @callback( [ - Output("monthly-solar", "children"), - Output("cloud-cover", "children"), + Output(ElementIds.MONTHLY_SOLAR, "children"), + Output(ElementIds.CLOUD_COVER, "children"), ], [ - Input("df-store", "modified_timestamp"), + Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUN_DF_STORE, "data"), + State(ElementIds.ID_SUN_META_STORE, "data"), + State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) def monthly_and_cloud_chart(_, df, meta, si_ip): @@ -239,17 +240,17 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): @callback( - Output("custom-sunpath", "children"), + Output(ElementIds.CUSTOM_SUNPATH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("custom-sun-view-dropdown", "value"), - Input("custom-sun-var-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), + Input(ElementIds.CUSTOM_SUN_VIEW_DROPDOWN, "value"), + Input(ElementIds.CUSTOM_SUN_VAR_DROPDOWN, "value"), + Input(ElementIds.ID_SUN_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUN_DF_STORE, "data"), + State(ElementIds.ID_SUN_META_STORE, "data"), + State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) def sun_path_chart(_, view, var, global_local, df, meta, si_ip): @@ -269,16 +270,16 @@ def sun_path_chart(_, view, var, global_local, df, meta, si_ip): @callback( - Output("tab4-daily", "children"), + Output(ElementIds.TAB4_DAILY, "children"), [ - Input("df-store", "modified_timestamp"), - Input("tab4-explore-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB_EXPLORE_DROPDOWN, "value"), + Input(ElementIds.ID_SUN_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUN_DF_STORE, "data"), + State(ElementIds.ID_SUN_META_STORE, "data"), + State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) def daily(_, var, global_local, df, meta, si_ip): @@ -292,16 +293,16 @@ def daily(_, var, global_local, df, meta, si_ip): @callback( - Output("tab4-heatmap", "children"), + Output(ElementIds.TAB4_HEATMAP, "children"), [ - Input("df-store", "modified_timestamp"), - Input("tab4-explore-dropdown", "value"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB_EXPLORE_DROPDOWN, "value"), + Input(ElementIds.ID_SUN_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUN_DF_STORE, "data"), + State(ElementIds.ID_SUN_META_STORE, "data"), + State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) def update_heatmap(_, var, global_local, df, meta, si_ip): diff --git a/pages/wind.py b/pages/wind.py index fe0d3da8..d123a0b3 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -1,6 +1,7 @@ import dash from dash import dcc, html from dash_extensions.enrich import Output, Input, State, callback +from pages.lib.global_column_names import ElementIds from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import month_lst, container_row_center_full @@ -28,14 +29,14 @@ def sliders(): """Returns 2 sliders for the hour""" return html.Div( className="container-col justify-center", - id="slider-container", + id=ElementIds.SLIDER_CONTAINER, children=[ html.Div( className="container-row each-slider", children=[ html.P("Month Range"), dcc.RangeSlider( - id="month-slider", + id=ElementIds.MONTH_SLIDER, min=1, max=12, step=1, @@ -51,7 +52,7 @@ def sliders(): children=[ html.P("Hour Range"), dcc.RangeSlider( - id="hour-slider", + id=ElementIds.HOUR_SLIDER, min=1, max=24, step=1, @@ -87,12 +88,12 @@ def seasonal_wind_rose(): dcc.Loading( type="circle", children=html.Div( - id="winter-wind-rose", + id=ElementIds.WINTER_WIND_ROSE, className="daily-wind-graph", ), ), html.P( - className="seasonal-text", id="winter-wind-rose-text" + className="seasonal-text", id=ElementIds.WINTER_WIND_ROSE_TEXT ), ], ), @@ -102,12 +103,12 @@ def seasonal_wind_rose(): dcc.Loading( type="circle", children=html.Div( - id="spring-wind-rose", + id=ElementIds.SPRING_WIND_ROSE, className="daily-wind-graph", ), ), html.P( - className="seasonal-text", id="spring-wind-rose-text" + className="seasonal-text", id=ElementIds.SPRING_WIND_ROSE_TEXT ), ], ), @@ -122,12 +123,12 @@ def seasonal_wind_rose(): dcc.Loading( type="circle", children=html.Div( - id="summer-wind-rose", + id= ElementIds.SUMMER_WIND_ROSE, className="daily-wind-graph", ), ), html.P( - className="seasonal-text", id="summer-wind-rose-text" + className="seasonal-text", id=ElementIds.SUMMER_WIND_ROSE_TEXT ), ], ), @@ -137,11 +138,11 @@ def seasonal_wind_rose(): dcc.Loading( type="circle", children=html.Div( - id="fall-wind-rose", + id=ElementIds.FALL_WIND_ROSE, className="daily-wind-graph", ), ), - html.P(className="seasonal-text", id="fall-wind-rose-text"), + html.P(className="seasonal-text", id=ElementIds.FALL_WIND_ROSE_TEXT), ], ), ], @@ -154,7 +155,7 @@ def daily_wind_rose(): """Return the section for the 3 daily wind rose graphs.""" return html.Div( className="container-col full-width", - id="tab5-daily-container", + id=ElementIds.TAB5_DAILY_CONTAINER, children=[ html.Div( children=title_with_link( @@ -164,7 +165,7 @@ def daily_wind_rose(): ), ), html.Div( - id="daily-wind-rose-outer-container", + id=ElementIds.DAILY_WIND_ROSE_OUTER_CONTAINER, className="container-row full-width", children=[ html.Div( @@ -175,11 +176,11 @@ def daily_wind_rose(): type="circle", children=html.Div( className="daily-wind-graph", - id="morning-wind-rose", + id=ElementIds.MORNING_WIND_ROSE, ), ), ), - html.P(className="daily-text", id="morning-wind-rose-text"), + html.P(className="daily-text", id=ElementIds.MORNING_WIND_ROSE_TEXT), ], ), html.Div( @@ -190,11 +191,11 @@ def daily_wind_rose(): type="circle", children=html.Div( className="daily-wind-graph", - id="noon-wind-rose", + id=ElementIds.NOON_WIND_ROSE, ), ), ), - html.P(className="daily-text", id="noon-wind-rose-text"), + html.P(className="daily-text", id=ElementIds.NOON_WIND_ROSE_TEXT), ], ), html.Div( @@ -205,11 +206,11 @@ def daily_wind_rose(): type="circle", children=html.Div( className="daily-wind-graph", - id="night-wind-rose", + id=ElementIds.NIGHT_WIND_ROSE, ), ), ), - html.P(className="daily-text", id="night-wind-rose-text"), + html.P(className="daily-text", id=ElementIds.NIGHT_WIND_ROSE_TEXT), ], ), ], @@ -231,7 +232,7 @@ def custom_wind_rose(): ), html.Div( className="container-row full-width justify-center", - id="tab5-custom-dropdown-container", + id=ElementIds.TAB5_CUSTOM_DROPDOWN_CONTAINER, children=[ html.Div( className="container-col justify-center p-2 mr-2", @@ -244,7 +245,7 @@ def custom_wind_rose(): children=["Start Month:"], ), dropdown( - id="tab5-custom-start-month", + id=ElementIds.TAB5_CUSTOM_START_MONTH, options={ j: i + 1 for i, j in enumerate(month_lst) }, @@ -261,7 +262,7 @@ def custom_wind_rose(): children=["Start Hour:"], ), dropdown( - id="tab5-custom-start-hour", + id=ElementIds.TAB5_CUSTOM_START_HOUR, options={ str(i) + ":00": i for i in range(0, 24) }, @@ -283,7 +284,7 @@ def custom_wind_rose(): children=["End Month:"], ), dropdown( - id="tab5-custom-end-month", + id=ElementIds.TAB5_CUSTOM_END_MONTH, options={ j: i + 1 for i, j in enumerate(month_lst) }, @@ -300,7 +301,7 @@ def custom_wind_rose(): children=["End Hour:"], ), dropdown( - id="tab5-custom-end-hour", + id=ElementIds.TAB5_CUSTOM_END_HOUR, options={ str(i) + ":00": i for i in range(1, 25) }, @@ -315,7 +316,7 @@ def custom_wind_rose(): ), dcc.Loading( type="circle", - children=html.Div(id="custom-wind-rose"), + children=html.Div(id=ElementIds.CUSTOM_WIND_ROSE), ), ], ) @@ -336,16 +337,16 @@ def layout(): dcc.Loading( type="circle", children=html.Div( - id="wind-rose", + id=ElementIds.WIND_ROSE, ), ), dcc.Loading( type="circle", - children=html.Div(id="wind-speed"), + children=html.Div(id=ElementIds.WIND_SPEED), ), dcc.Loading( type="circle", - children=html.Div(id="wind-direction"), + children=html.Div(id=ElementIds.WIND_DIRECTION), ), seasonal_wind_rose(), daily_wind_rose(), @@ -356,12 +357,12 @@ def layout(): # wind rose @callback( - Output("wind-rose", "children"), - Input("df-store", "modified_timestamp"), + Output(ElementIds.WIND_ROSE, "children"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_annual_wind_rose(_, df, meta, si_ip): @@ -380,13 +381,13 @@ def update_annual_wind_rose(_, df, meta, si_ip): Output(ColNames.WIND_SPEED, "children"), # General [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_WINDGLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_wind_speed(_, global_local, df, meta, si_ip): @@ -402,15 +403,15 @@ def update_tab_wind_speed(_, global_local, df, meta, si_ip): # wind direction @callback( - Output("wind-direction", "children"), + Output(ElementIds.WIND_DIRECTION, "children"), # General [ - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_WINDGLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_wind_direction(global_local, df, meta, si_ip): @@ -426,19 +427,19 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): # Custom Wind rose @callback( - Output("custom-wind-rose", "children"), + Output(ElementIds.CUSTOM_WIND_ROSE, "children"), # Custom Graph Input [ - Input("df-store", "modified_timestamp"), - Input("tab5-custom-start-month", "value"), - Input("tab5-custom-start-hour", "value"), - Input("tab5-custom-end-month", "value"), - Input("tab5-custom-end-hour", "value"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), + Input(ElementIds.TAB5_CUSTOM_START_MONTH, "value"), + Input(ElementIds.TAB5_CUSTOM_START_HOUR, "value"), + Input(ElementIds.TAB5_CUSTOM_END_MONTH, "value"), + Input(ElementIds.TAB5_CUSTOM_END_HOUR, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_custom_wind_rose( @@ -475,22 +476,22 @@ def update_custom_wind_rose( @callback( [ - Output("winter-wind-rose", "children"), - Output("spring-wind-rose", "children"), - Output("summer-wind-rose", "children"), - Output("fall-wind-rose", "children"), - Output("winter-wind-rose-text", "children"), - Output("spring-wind-rose-text", "children"), - Output("summer-wind-rose-text", "children"), - Output("fall-wind-rose-text", "children"), + Output(ElementIds.WINTER_WIND_ROSE, "children"), + Output(ElementIds.SPRING_WIND_ROSE, "children"), + Output(ElementIds.SUMMER_WIND_ROSE, "children"), + Output(ElementIds.FALL_WIND_ROSE, "children"), + Output(ElementIds.WINTER_WIND_ROSE_TEXT, "children"), + Output(ElementIds.SPRING_WIND_ROSE_TEXT, "children"), + Output(ElementIds.SUMMER_WIND_ROSE_TEXT, "children"), + Output(ElementIds.FALL_WIND_ROSE_TEXT, "children"), ], [ - Input("df-store", "modified_timestamp"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_seasonal_graphs(_, df, meta, si_ip): @@ -591,19 +592,19 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): @callback( # Daily Graphs [ - Output("morning-wind-rose", "children"), - Output("noon-wind-rose", "children"), - Output("night-wind-rose", "children"), - Output("morning-wind-rose-text", "children"), - Output("noon-wind-rose-text", "children"), - Output("night-wind-rose-text", "children"), + Output(ElementIds.MORNING_WIND_ROSE, "children"), + Output(ElementIds.NOON_WIND_ROSE, "children"), + Output(ElementIds.NIGHT_WIND_ROSE, "children"), + Output(ElementIds.MORNING_WIND_ROSE_TEXT, "children"), + Output(ElementIds.NOON_WIND_ROSE_TEXT, "children"), + Output(ElementIds.NIGHT_WIND_ROSE_TEXT, "children"), ], # General - Input("df-store", "modified_timestamp"), + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_WIND_DF_STORE, "data"), + State(ElementIds.ID_WIND_META_STORE, "data"), + State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) def update_daily_graphs(_, df, meta, si_ip): From da93f7f349d5e409f75b1ea9a78651d586895658 Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Sun, 24 Aug 2025 19:46:26 +1000 Subject: [PATCH 029/163] Fix: 1.add a new python file "global_elementids.py" and move the class ElementIds to this file 2.updated the ElementIDs of the explorer.py,global_column_names.py,natural_ventilation.py,outdoor.py,psy-chart.py,select.py,summary.py,sun.py,t_rh.py,wind.py --- pages/explorer.py | 8 +- pages/lib/global_column_names.py | 164 ------------------------- pages/lib/global_elementids.py | 200 +++++++++++++++++++++++++++++++ pages/natural_ventilation.py | 23 ++-- pages/outdoor.py | 4 +- pages/psy-chart.py | 2 +- pages/select.py | 77 ++++++------ pages/summary.py | 143 +++++++++++----------- pages/sun.py | 2 +- pages/t_rh.py | 61 +++++----- pages/wind.py | 6 +- 11 files changed, 365 insertions(+), 325 deletions(-) create mode 100644 pages/lib/global_elementids.py diff --git a/pages/explorer.py b/pages/explorer.py index 5a5ed6e0..92377385 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -12,7 +12,7 @@ two_var_graph, three_var_graph, ) -from pages.lib.global_column_names import ElementIds +from pages.lib.global_elementids import ElementIds from pages.lib.global_scheme import ( fig_config, dropdown_names, @@ -626,9 +626,9 @@ def layout(): Output(ElementIds.YEARLY_EXPLORE, "children"), # Section One [ - Input("df-store", "modified_timestamp"), + Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), @@ -685,7 +685,7 @@ def update_tab_daily(_, var, global_local, df, meta, si_ip): [ Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), - Input(ElementIds.ID_NATURAL_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index c959918e..6babc9a7 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -49,172 +49,8 @@ class ColNames(str, Enum): DOY = "DOY" # Day of Year -class ElementIds: - # ==================== Defines the unique ID constant for each element in the front-end page ==================== - OUTDOOR_COMFORT_OUTPUT = "outdoor-comfort-output" - DAILY = "daily" - DF_STORE = "df-store" - ENABLE_CONDENSATION = "enable-condensation" - ID_EXPLORER_DF_STORE = "df-store" - ID_EXPLORER_META_STORE = "meta-store" - ID_NATURAL_DF_STORE = "df-store" - ID_NATURAL_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - DROPDOWN = "dropdown" - CUSTOM_SUMMARY = "custom-summary" - CUSTOM_HEATMAP = "custom-heatmap" - GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - HEATMAP = "heatmap" - MAIN_NV_SECTION = "main-nv-section" - ID_NATURAL_META_STORE = "meta-store" - NORMALIZE = "normalize" - NV_BAR_CHART = "nv-bar-chart" - NV_DBT_FILTER = "nv-dbt-filter" - NV_DPT_FILTER = "nv-dpt-filter" - NV_DPT_MAX_VAL = "nv-dpt-max-val" - NV_TDB_MIN_VAL = "nv-tdb-min-val" - NV_TDB_MAX_VAL = "nv-tdb-max-val" - NV_HEATMAP_CHART = "nv-heatmap-chart" - OUTDOOR_COMFORT_HOUR_SLIDER = "outdoor-comfort-hour-slider" - NV_MONTH_HOUR_FILTER = "nv-month-hour-filter" - NV_MONTH_SLIDER = "nv-month-slider" - IMAGE_SELECTION = "image-selection" - INVERT_HOUR_OUTDOOR_COMFORT = "invert-hour-outdoor-comfort" - INVERT_MONTH_OUTDOOR_COMFORT = "invert-month-outdoor-comfort" - INVERT_MONTH_NV = "invert-month-nv" - ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - INVERT_HOUR_EXPLORE_DESCRIPTIVE = "invert-hour-explore-descriptive" - INVERT_MONTH_EXPLORE_DESCRIPTIVE = "invert-month-explore-descriptive" - INVERT_MONTH_EXPLORE_HEATMAP = "invert-month-explore-heatmap" - INVERT_HOUR_EXPLORE_HEATMAP = "invert-hour-explore-heatmap" - INVERT_MONTH_EXPLORE_MORE_CHARTS = "invert-month-explore-more-charts" - INVERT_HOUR_EXPLORE_MORE_CHARTS = "invert-hour-explore-more-charts" - INVERT_HOUR_NV = "invert-hour-nv" - ID_EXPLORER_SI_IP_UNIT_STORE = "si-ip-unit-store" - ID_NATURAL_SI_IP_UNIT_STORE = "si-ip-unit-store" - SI_IP_RADIO_INPUT = "si-ip-radio-input" - META_STORE = "meta-store" - OUTDOOR_COMFORT_MONTH_SLIDER = "outdoor-comfort-month-slider" - OUTDOOR_COMFORT_SWITCHES_INPUT = "outdoor-comfort-switches-input" - NV_HOUR_SLIDER = "nv-hour-slider" - PSYCH_CHART = "psych-chart" - PSY_CHART_BTN = "psy-chart-btn" - PSY_COLOR_BY_DROPDOWN = "psy-color-by-dropdown" - DATA_FILTER = "data-filter" - PSY_VAR_DROPDOWN = "psy-var-dropdown" - PSY_HOUR_SLIDER = "psy-hour-slider" - INVERT_HOUR_PSY = "invert-hour-psy" - INVERT_MONTH_PSY = "invert-month-psy" - PSY_MAX_VAL = "psy-max-val" - PSY_MIN_VAL = "psy-min-val" - PSY_MONTH_SLIDER = "psy-month-slider" - ID_OUTDOOR_MONTH_HOUR_FILTER = "month-hour-filter" - MONTH_HOUR_FILTER = "month-hour-filter" - SEC1_HOUR_SLIDER = "sec1-hour-slider" - SEC1_MONTH_SLIDER = "sec1-month-slider" - SEC1_TIME_FILTER_INPUT = "sec1-time-filter-input" - SEC1_VAR_DROPDOWN = "sec1-var-dropdown" - SDATA_FILTER = "data-filter" - SEC2_DATA_FILTER_INPUT = "sec2-data-filter-input" - SEC2_DATA_FILTER_VAR = "sec2-data-filter-var" - SEC2_VAR_DROPDOWN = "sec2-var-dropdown" - SEC2_HOUR_SLIDER = "sec2-hour-slider" - SEC2_TIME_FILTER_INPUT = "sec2-time-filter-input" - SEC2_MONTH_SLIDER = "sec2-month-slider" - SEC2_MIN_VAL = "sec2-min-val" - SEC2_MAX_VAL = "sec2-max-val" - TAB7_DROPDOWN = "tab7-dropdown" - SI_IP_UNIT_STORE = "si-ip-unit-store" - SWITCHES_INPUT = "switches-input" - TABLE_TMP_HUM = "table-tmp-hum" - TABLE_DATA_EXPLORER = "table-data-explorer" - TAB6_SEC2_CONTAINER = "tab6-sec2-container" - TAB6_SEC3_DATA_FILTER_INPUT = "tab6-sec3-data-filter-input" - TAB6_SEC3_FILTER_VAR_DROPDOWN = "tab6-sec3-filter-var-dropdown" - TAB6_SEC3_MIN_VAL = "tab6-sec3-min-val" - TAB6_SEC3_MAX_VAL = "tab6-sec3-max-val" - TAB6_SEC3_TIME_FILTER_INPUT = "tab6-sec3-time-filter-input" - TAB6_SEC3_QUERY_HOUR_SLIDER = "tab6-sec3-query-hour-slider" - TAB6_SEC3_QUERY_MONTH_SLIDER = "tab6-sec3-query-month-slider" - TAB6_SEC3_VAR_X_DROPDOWN = "tab6-sec3-var-x-dropdown" - TAB6_SEC3_VAR_Y_DROPDOWN = "tab6-sec3-var-y-dropdown" - TAB6_SEC3_COLORBY_DROPDOWN = "tab6-sec3-colorby-dropdown" - MONTH_HOUR_FILTER_OUTDOOR_COMFORT = "month-hour-filter-outdoor-comfort" - TWO_VAR = "two-var" - THREE_VAR = "three-var" - QUERY_DAILY = "query-daily" - QUERY_HEATMAP = "query-heatmap" - UTCI_CATEGORY_HEATMAP = "utci-category-heatmap" - UTCI_HEATMAP = "utci-heatmap" - UTCI_SUMMARY_CHART = "utci-summary-chart" - YEARLY_CHART = "yearly-chart" - YEARLY_EXPLORE = "yearly-explore" - ID_OUTDOOR_DF_STORE = "df-store" - ID_OUTDOOR_META_STORE = "meta-store" - ID_OUTDOOR_SI_IP_UNIT_STORE = "si-ip-unit-store" - ID_PSY_CHART_SI_IP_UNIT_STORE = "si-ip-unit-store" - ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - ID_PSY_CHART_DF_STORE = "df-store" - ID_PSY_CHART_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - ID_PSY_CHART_META_STORE = "meta-store" - CUSTOM_SUN_VIEW_DROPDOWN = "custom-sun-view-dropdown" - CUSTOM_SUN_VAR_DROPDOWN = "custom-sun-var-dropdown" - CUSTOM_SUNPATH = "custom-sunpath" - TAB_EXPLORE_DROPDOWN = "tab4-explore-dropdown" - TAB4_DAILY = "tab4-daily" - TAB4_HEATMAP = "tab4-heatmap" - STATIC_SECTION = "static-section" - TAB_FOUR_CONTAINER = "tab-four-container" - MONTHLY_SOLAR = "monthly-solar" - CLOUD_COVER = "cloud-cover" - ID_SUN_SI_IP_RADIO_INPUT = "si-ip-radio-input" - ID_SUN_DF_STORE = "df-store" - ID_SUN_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - ID_SUN_SI_IP_UNIT_STORE = "si-ip-unit-store" - ID_SUN_META_STORE = "meta-store" - SLIDER_CONTAINER = "slider-container" - MONTH_SLIDER = "month-slider" - HOUR_SLIDER = "hour-slider" - WINTER_WIND_ROSE = "winter-wind-rose" - WINTER_WIND_ROSE_TEXT = "winter-wind-rose-text" - SPRING_WIND_ROSE = "spring-wind-rose" - SPRING_WIND_ROSE_TEXT = "spring-wind-rose-text" - SUMMER_WIND_ROSE = "summer-wind-rose" - SUMMER_WIND_ROSE_TEXT = "summer-wind-rose-text" - FALL_WIND_ROSE = "fall-wind-rose" - FALL_WIND_ROSE_TEXT = "fall-wind-rose-text" - TAB5_DAILY_CONTAINER = "tab5-daily-container" - DAILY_WIND_ROSE_OUTER_CONTAINER = "daily-wind-rose-outer-container" - MORNING_WIND_ROSE = "morning-wind-rose" - MORNING_WIND_ROSE_TEXT = "morning-wind-rose-text" - NOON_WIND_ROSE = "noon-wind-rose" - NOON_WIND_ROSE_TEXT = "noon-wind-rose-text" - NIGHT_WIND_ROSE = "night-wind-rose" - NIGHT_WIND_ROSE_TEXT = "night-wind-rose-text" - TAB5_CUSTOM_DROPDOWN_CONTAINER = "tab5-custom-dropdown-container" - TAB5_CUSTOM_START_MONTH = "tab5-custom-start-month" - TAB5_CUSTOM_START_HOUR = "tab5-custom-start-hour" - TAB5_CUSTOM_END_MONTH = "tab5-custom-end-month" - TAB5_CUSTOM_END_HOUR = "tab5-custom-end-hour" - CUSTOM_WIND_ROSE = "custom-wind-rose" - WIND_ROSE = "wind-rose" - WIND_SPEED = "wind-speed" - WIND_DIRECTION = "wind-direction" - ID_WIND_DF_STORE = "df-store" - ID_WIND_META_STORE = "meta-store" - ID_WIND_SI_IP_UNIT_STORE = "si-ip-unit-store" - ID_WINDGLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" -class ComponentProperty: - # ==================== Define common attribute name constants for components ==================== - CHILDREN = "children" - DATA = "data" - MODIFIED_TIMESTAMP = "modified_timestamp" - VALUE = "value" - -class Type: - # ==================== Defines type constants available for UI components ==================== - CIRCLE = "circle" diff --git a/pages/lib/global_elementids.py b/pages/lib/global_elementids.py new file mode 100644 index 00000000..6fcce955 --- /dev/null +++ b/pages/lib/global_elementids.py @@ -0,0 +1,200 @@ +from enum import Enum + + +class ElementIds(str, Enum): + # ==================== Defines the unique ID constant for each element in the front-end page ==================== + OUTDOOR_COMFORT_OUTPUT = "outdoor-comfort-output" + DAILY = "daily" + ID_T_RH_DF_STORE = "df-store" + ENABLE_CONDENSATION = "enable-condensation" + ID_EXPLORER_DF_STORE = "df-store" + ID_EXPLORER_META_STORE = "meta-store" + ID_NATURAL_VENTILATION_DF_STORE = "df-store" + ID_NATURAL_VENTILATION_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_T_RH_DROPDOWN = "dropdown" + CUSTOM_SUMMARY = "custom-summary" + CUSTOM_HEATMAP = "custom-heatmap" + ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + HEATMAP = "heatmap" + MAIN_NV_SECTION = "main-nv-section" + ID_NATURAL_VENTILATION_META_STORE = "meta-store" + NORMALIZE = "normalize" + NV_BAR_CHART = "nv-bar-chart" + NV_DBT_FILTER = "nv-dbt-filter" + NV_DPT_FILTER = "nv-dpt-filter" + NV_DPT_MAX_VAL = "nv-dpt-max-val" + NV_TDB_MIN_VAL = "nv-tdb-min-val" + NV_TDB_MAX_VAL = "nv-tdb-max-val" + NV_HEATMAP_CHART = "nv-heatmap-chart" + OUTDOOR_COMFORT_HOUR_SLIDER = "outdoor-comfort-hour-slider" + NV_MONTH_HOUR_FILTER = "nv-month-hour-filter" + NV_MONTH_SLIDER = "nv-month-slider" + IMAGE_SELECTION = "image-selection" + INVERT_HOUR_OUTDOOR_COMFORT = "invert-hour-outdoor-comfort" + INVERT_MONTH_OUTDOOR_COMFORT = "invert-month-outdoor-comfort" + INVERT_MONTH_NV = "invert-month-nv" + ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + INVERT_HOUR_EXPLORE_DESCRIPTIVE = "invert-hour-explore-descriptive" + INVERT_MONTH_EXPLORE_DESCRIPTIVE = "invert-month-explore-descriptive" + INVERT_MONTH_EXPLORE_HEATMAP = "invert-month-explore-heatmap" + INVERT_HOUR_EXPLORE_HEATMAP = "invert-hour-explore-heatmap" + INVERT_MONTH_EXPLORE_MORE_CHARTS = "invert-month-explore-more-charts" + INVERT_HOUR_EXPLORE_MORE_CHARTS = "invert-hour-explore-more-charts" + INVERT_HOUR_NV = "invert-hour-nv" + ID_EXPLORER_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_NATURAL_VENTILATION_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_NATURAL_VENTILATION_SI_IP_RADIO_INPUT = "si-ip-radio-input" + ID_T_RH_META_STORE = "meta-store" + OUTDOOR_COMFORT_MONTH_SLIDER = "outdoor-comfort-month-slider" + OUTDOOR_COMFORT_SWITCHES_INPUT = "outdoor-comfort-switches-input" + NV_HOUR_SLIDER = "nv-hour-slider" + PSYCH_CHART = "psych-chart" + PSY_CHART_BTN = "psy-chart-btn" + PSY_COLOR_BY_DROPDOWN = "psy-color-by-dropdown" + DATA_FILTER = "data-filter" + PSY_VAR_DROPDOWN = "psy-var-dropdown" + PSY_HOUR_SLIDER = "psy-hour-slider" + INVERT_HOUR_PSY = "invert-hour-psy" + INVERT_MONTH_PSY = "invert-month-psy" + PSY_MAX_VAL = "psy-max-val" + PSY_MIN_VAL = "psy-min-val" + PSY_MONTH_SLIDER = "psy-month-slider" + ID_OUTDOOR_MONTH_HOUR_FILTER = "month-hour-filter" + MONTH_HOUR_FILTER = "month-hour-filter" + SEC1_HOUR_SLIDER = "sec1-hour-slider" + SEC1_MONTH_SLIDER = "sec1-month-slider" + SEC1_TIME_FILTER_INPUT = "sec1-time-filter-input" + SEC1_VAR_DROPDOWN = "sec1-var-dropdown" + SDATA_FILTER = "data-filter" + SEC2_DATA_FILTER_INPUT = "sec2-data-filter-input" + SEC2_DATA_FILTER_VAR = "sec2-data-filter-var" + SEC2_VAR_DROPDOWN = "sec2-var-dropdown" + SEC2_HOUR_SLIDER = "sec2-hour-slider" + SEC2_TIME_FILTER_INPUT = "sec2-time-filter-input" + SEC2_MONTH_SLIDER = "sec2-month-slider" + SEC2_MIN_VAL = "sec2-min-val" + SEC2_MAX_VAL = "sec2-max-val" + TAB7_DROPDOWN = "tab7-dropdown" + ID_T_RH_SI_IP_UNIT_STORE = "si-ip-unit-store" + SWITCHES_INPUT = "switches-input" + TABLE_TMP_HUM = "table-tmp-hum" + TABLE_DATA_EXPLORER = "table-data-explorer" + TAB6_SEC2_CONTAINER = "tab6-sec2-container" + TAB6_SEC3_DATA_FILTER_INPUT = "tab6-sec3-data-filter-input" + TAB6_SEC3_FILTER_VAR_DROPDOWN = "tab6-sec3-filter-var-dropdown" + TAB6_SEC3_MIN_VAL = "tab6-sec3-min-val" + TAB6_SEC3_MAX_VAL = "tab6-sec3-max-val" + TAB6_SEC3_TIME_FILTER_INPUT = "tab6-sec3-time-filter-input" + TAB6_SEC3_QUERY_HOUR_SLIDER = "tab6-sec3-query-hour-slider" + TAB6_SEC3_QUERY_MONTH_SLIDER = "tab6-sec3-query-month-slider" + TAB6_SEC3_VAR_X_DROPDOWN = "tab6-sec3-var-x-dropdown" + TAB6_SEC3_VAR_Y_DROPDOWN = "tab6-sec3-var-y-dropdown" + TAB6_SEC3_COLORBY_DROPDOWN = "tab6-sec3-colorby-dropdown" + MONTH_HOUR_FILTER_OUTDOOR_COMFORT = "month-hour-filter-outdoor-comfort" + TWO_VAR = "two-var" + THREE_VAR = "three-var" + QUERY_DAILY = "query-daily" + QUERY_HEATMAP = "query-heatmap" + UTCI_CATEGORY_HEATMAP = "utci-category-heatmap" + UTCI_HEATMAP = "utci-heatmap" + UTCI_SUMMARY_CHART = "utci-summary-chart" + YEARLY_CHART = "yearly-chart" + YEARLY_EXPLORE = "yearly-explore" + ID_OUTDOOR_DF_STORE = "df-store" + ID_OUTDOOR_META_STORE = "meta-store" + ID_OUTDOOR_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_PSY_CHART_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_PSY_CHART_DF_STORE = "df-store" + ID_PSY_CHART_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_PSY_CHART_META_STORE = "meta-store" + CUSTOM_SUN_VIEW_DROPDOWN = "custom-sun-view-dropdown" + CUSTOM_SUN_VAR_DROPDOWN = "custom-sun-var-dropdown" + CUSTOM_SUNPATH = "custom-sunpath" + TAB_EXPLORE_DROPDOWN = "tab4-explore-dropdown" + TAB4_DAILY = "tab4-daily" + TAB4_HEATMAP = "tab4-heatmap" + STATIC_SECTION = "static-section" + TAB_FOUR_CONTAINER = "tab-four-container" + MONTHLY_SOLAR = "monthly-solar" + CLOUD_COVER = "cloud-cover" + ID_SUN_SI_IP_RADIO_INPUT = "si-ip-radio-input" + ID_SUN_DF_STORE = "df-store" + ID_SUN_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + ID_SUN_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_SUN_META_STORE = "meta-store" + SLIDER_CONTAINER = "slider-container" + MONTH_SLIDER = "month-slider" + HOUR_SLIDER = "hour-slider" + WINTER_WIND_ROSE = "winter-wind-rose" + WINTER_WIND_ROSE_TEXT = "winter-wind-rose-text" + SPRING_WIND_ROSE = "spring-wind-rose" + SPRING_WIND_ROSE_TEXT = "spring-wind-rose-text" + SUMMER_WIND_ROSE = "summer-wind-rose" + SUMMER_WIND_ROSE_TEXT = "summer-wind-rose-text" + FALL_WIND_ROSE = "fall-wind-rose" + FALL_WIND_ROSE_TEXT = "fall-wind-rose-text" + TAB5_DAILY_CONTAINER = "tab5-daily-container" + DAILY_WIND_ROSE_OUTER_CONTAINER = "daily-wind-rose-outer-container" + MORNING_WIND_ROSE = "morning-wind-rose" + MORNING_WIND_ROSE_TEXT = "morning-wind-rose-text" + NOON_WIND_ROSE = "noon-wind-rose" + NOON_WIND_ROSE_TEXT = "noon-wind-rose-text" + NIGHT_WIND_ROSE = "night-wind-rose" + NIGHT_WIND_ROSE_TEXT = "night-wind-rose-text" + TAB5_CUSTOM_DROPDOWN_CONTAINER = "tab5-custom-dropdown-container" + TAB5_CUSTOM_START_MONTH = "tab5-custom-start-month" + TAB5_CUSTOM_START_HOUR = "tab5-custom-start-hour" + TAB5_CUSTOM_END_MONTH = "tab5-custom-end-month" + TAB5_CUSTOM_END_HOUR = "tab5-custom-end-hour" + CUSTOM_WIND_ROSE = "custom-wind-rose" + WIND_ROSE = "wind-rose" + WIND_SPEED = "wind-speed" + WIND_DIRECTION = "wind-direction" + ID_WIND_DF_STORE = "df-store" + 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" + LOADING_ONE = "loading-1" + UPLOAD_DATA = "upload-data" + UPLOAD_DATA_BUTTON = "upload-data-button" + TAB_ONE_MAP = "tab-one-map" + SKELETON_GRAPH_CONTAINER = "skeleton-graph-container" + MODAL_HEADER = "modal-header" + MODAL_CLOSE_BUTTON = "modal-close-button" + MODAL_YES_BUTTON = "modal-yes-button" + MODAL = "modal" + ALERT = "alert" + ID_SELECT_META_STORE = "meta-store" + LINES_STORE = "lines-store" + ID_SELECT_URL_STORE = "url-store" + ID_SELECT_DF_STORE = "df-store" + ID_SELECT_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_SELECT_SI_IP_RADIO_INPUT = "si-ip-radio-input" + BANNER_SUBTITLE = "banner-subtitle" + TABLE_TWO_CONTAINER = "tab-two-container" + ID_SUMMARY_SI_IP_RADIO_INPUT = "si-ip-radio-input" + TAB2_SCE1_CONTAINER = "tab2-sec1-container" + LOCATION_INFO = "location-info" + WORLD_MAP = "world-map" + DOWN_EPW_BUTTON = "download-epw-button" + DOWNLOAD_BUTTON = "download-button" + DOWNLOAD_DATAFRAME_CSV = "download-dataframe-csv" + DOWNLOAD_EPW = "download-epw" + WARNING_CDD_HIGHER_HDD = "warning-cdd-higher-hdd" + INPUT_HDD_SET_POINT = "input-hdd-set-point" + INPUT_CDD_SET_POINT = "input-cdd-set-point" + SUBMIT_SET_POINTS = "submit-set-points" + GRAPH_CONTAINER = "graph-container" + DEGREE_DAYS_CHART_WRAPPER = "degree-days-chart-wrapper" + TEMP_PROFILE_GRAPH = "temp-profile-graph" + HUMIDITY_PROFILE_GRAPH = "humidity-profile-graph" + SOLAR_RADIATION_GRAPH = "solar-radiation-graph" + WIND_SPEED_GRAPH = "wind-speed-graph" + ID_SUMMARY_META_STORE = "meta-store" + GH_RAD_PROFILE_GRAPH = "gh_rad-profile-graph" + ID_SUMMARY_DF_STORE = "df-store" + ID_SUMMARY_SI_IP_UNIT_STORE = "si-ip-unit-store" + ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + TDB_PROFILE_GRAPH = "tdb-profile-graph" + RH_PROFILE_GRAPH = "rh-profile-graph" diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 3c16861c..c772c0b4 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -19,7 +19,8 @@ container_col_center_one_of_three, ) from pages.lib.template_graphs import filter_df_by_month_and_hour -from pages.lib.global_column_names import ColNames, ElementIds +from pages.lib.global_column_names import ColNames +from pages.lib.global_elementids import ElementIds from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -51,7 +52,7 @@ def layout(): @callback( Output(ElementIds.MAIN_NV_SECTION, "children"), - [Input(ElementIds.SI_IP_RADIO_INPUT, "value")], + [Input(ElementIds.ID_NATURAL_VENTILATION_SI_IP_RADIO_INPUT, "value")], ) def update_layout(si_ip): if si_ip == UnitSystem.IP: @@ -286,24 +287,24 @@ def inputs_tab(t_min, t_max, d_set): @callback( Output(ElementIds.NV_HEATMAP_CHART, "children"), [ - Input(ElementIds.ID_NATURAL_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "modified_timestamp"), Input(ElementIds.NV_MONTH_HOUR_FILTER, "n_clicks"), Input(ElementIds.NV_DBT_FILTER, "n_clicks"), Input(ElementIds.NV_DPT_FILTER, "n_clicks"), - Input(ElementIds.ID_NATURAL_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_NATURAL_VENTILATION_GLOBAL_LOCAL_RADIO_INPUT, "value"), Input(ElementIds.ENABLE_CONDENSATION, "value"), ], [ - State(ElementIds.ID_NATURAL_DF_STORE, "data"), + State(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "data"), State(ElementIds.NV_MONTH_SLIDER, "value"), State(ElementIds.NV_HOUR_SLIDER, "value"), State(ElementIds.NV_TDB_MIN_VAL, "value"), State(ElementIds.NV_TDB_MAX_VAL, "value"), State(ElementIds.NV_DPT_MAX_VAL, "value"), - State(ElementIds.ID_NATURAL_META_STORE, "data"), + State(ElementIds.ID_NATURAL_VENTILATION_META_STORE, "data"), State(ElementIds.INVERT_MONTH_NV, "value"), State(ElementIds.INVERT_HOUR_NV, "value"), - State(ElementIds.ID_NATURAL_SI_IP_UNIT_STORE, "data"), + State(ElementIds.ID_NATURAL_VENTILATION_SI_IP_UNIT_STORE, "data"), ], ) def nv_heatmap( @@ -453,7 +454,7 @@ def nv_heatmap( @callback( Output(ElementIds.NV_BAR_CHART, "children"), [ - Input(ElementIds.ID_NATURAL_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "modified_timestamp"), Input(ElementIds.NV_MONTH_HOUR_FILTER, "n_clicks"), Input(ElementIds.NV_DBT_FILTER, "n_clicks"), Input(ElementIds.NV_DPT_FILTER, "n_clicks"), @@ -461,16 +462,16 @@ def nv_heatmap( Input(ElementIds.ENABLE_CONDENSATION, "value"), ], [ - State(ElementIds.ID_NATURAL_DF_STORE, "data"), + State(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "data"), State(ElementIds.NV_MONTH_SLIDER, "value"), State(ElementIds.NV_HOUR_SLIDER, "value"), State(ElementIds.NV_TDB_MIN_VAL, "value"), State(ElementIds.NV_TDB_MAX_VAL, "value"), State(ElementIds.NV_DPT_MAX_VAL, "value"), - State(ElementIds.ID_NATURAL_META_STORE, "data"), + State(ElementIds.ID_NATURAL_VENTILATION_META_STORE, "data"), State(ElementIds.INVERT_MONTH_NV, "value"), State(ElementIds.INVERT_HOUR_NV, "value"), - State(ElementIds.ID_NATURAL_SI_IP_UNIT_STORE, "data"), + State(ElementIds.ID_NATURAL_VENTILATION_SI_IP_UNIT_STORE, "data"), ], ) def nv_bar_chart( diff --git a/pages/outdoor.py b/pages/outdoor.py index d6a805be..eb2350f8 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -6,7 +6,7 @@ import numpy as np from config import PageUrls, DocLinks, PageInfo -from pages.lib.global_column_names import ElementIds, ComponentProperty, Type +from pages.lib.global_elementids import ElementIds from pages.lib.global_scheme import ( outdoor_dropdown_names, ) @@ -316,7 +316,7 @@ def change_image_based_on_selection(value): [ Input(ElementIds.ID_OUTDOOR_DF_STORE, "modified_timestamp"), Input(ElementIds.TAB7_DROPDOWN, "value"), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT, "value"), Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), ], [ diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 83ba076f..51bac4cc 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -12,7 +12,7 @@ from pythermalcomfort import psychrometrics as psy from config import PageUrls, DocLinks, PageInfo, UnitSystem -from pages.lib.global_column_names import ElementIds, ComponentProperty, Type +from pages.lib.global_elementids import ElementIds from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, diff --git a/pages/select.py b/pages/select.py index cfc770d7..d0e27df9 100644 --- a/pages/select.py +++ b/pages/select.py @@ -13,6 +13,7 @@ from pages.lib.extract_df import convert_data from pages.lib.extract_df import create_df, get_data, get_location_info +from pages.lib.global_elementids import ElementIds from pages.lib.global_scheme import mapping_dictionary from config import PageUrls, PageInfo, UnitSystem from pages.lib.utils import generate_chart_name @@ -40,19 +41,19 @@ def layout(): className="container-col tab-container", children=[ dcc.Loading( - id="loading-1", + id=ElementIds.LOADING_ONE, type="circle", fullscreen=True, children=alert(), ), dcc.Upload( - id="upload-data", + id=ElementIds.UPLOAD_DATA, children=dbc.Button( [ "Drag and Drop or ", html.A("Select an EPW file from your computer"), ], - id="upload-data-button", + id=ElementIds.UPLOAD_DATA_BUTTON, outline=True, color="secondary", className="mt-2", @@ -64,31 +65,31 @@ def layout(): ), dmc.Skeleton( visible=False, - id="skeleton-graph-container", + id=ElementIds.SKELETON_GRAPH_CONTAINER, height=500, - children=html.Div(id="tab-one-map"), + children=html.Div(id=ElementIds.TAB_ONE_MAP), ), dbc.Modal( [ - dbc.ModalHeader(id="modal-header"), + dbc.ModalHeader(id=ElementIds.MODAL_HEADER), dbc.ModalFooter( children=[ dbc.Button( "Close", - id="modal-close-button", + id=ElementIds.MODAL_CLOSE_BUTTON, className="ml-2", color="light", ), dbc.Button( "Yes", - id="modal-yes-button", + id=ElementIds.MODAL_YES_BUTTON, className="ml-2", color="primary", ), ] ), ], - id="modal", + id=ElementIds.MODAL, is_open=False, ), ], @@ -100,7 +101,7 @@ def alert(): return dbc.Alert( messages_alert["start"], color="primary", - id="alert", + id=ElementIds.ALERT, dismissable=False, is_open=True, style={"maxHeight": "66px"}, @@ -110,20 +111,20 @@ def alert(): # add si-ip and map dictionary in the output @callback( [ - Output("meta-store", "data"), - Output("lines-store", "data"), - Output("alert", "is_open"), - Output("alert", "children"), - Output("alert", "color"), + Output(ElementIds.ID_SELECT_META_STORE, "data"), + Output(ElementIds.LINES_STORE, "data"), + Output(ElementIds.ALERT, "is_open"), + Output(ElementIds.ALERT, "children"), + Output(ElementIds.ALERT, "color"), ], [ - Input("modal-yes-button", "n_clicks"), - Input("upload-data-button", "n_clicks"), - Input("upload-data", "contents"), + Input(ElementIds.MODAL_YES_BUTTON, "n_clicks"), + Input(ElementIds.UPLOAD_DATA_BUTTON, "n_clicks"), + Input(ElementIds.UPLOAD_DATA, "contents"), ], [ - State("upload-data", "filename"), - State("url-store", "data"), + State(ElementIds.UPLOAD_DATA, "filename"), + State(ElementIds.ID_SELECT_URL_STORE, "data"), ], prevent_initial_call=True, ) @@ -205,14 +206,14 @@ def submitted_data( # add switch_si_ip function and convert the data-store @callback( [ - Output("df-store", "data"), - Output("si-ip-unit-store", "data"), + Output(ElementIds.ID_SELECT_DF_STORE, "data"), + Output(ElementIds.ID_SELECT_SI_IP_UNIT_STORE, "data"), ], [ - Input("lines-store", "modified_timestamp"), - Input("si-ip-radio-input", "value"), + Input(ElementIds.LINES_STORE, "modified_timestamp"), + Input(ElementIds.ID_SELECT_SI_IP_RADIO_INPUT, "value"), ], - [State("url-store", "data"), State("lines-store", "data")], + [State(ElementIds.ID_SELECT_URL_STORE, "data"), State("lines-store", "data")], ) def switch_si_ip(_, si_ip_input, url_store, lines): if lines is not None: @@ -239,11 +240,11 @@ def switch_si_ip(_, si_ip_input, url_store, lines): Output("/explorer", "disabled"), Output("/outdoor", "disabled"), Output("/natural-ventilation", "disabled"), - Output("banner-subtitle", "children"), + Output(ElementIds.BANNER_SUBTITLE, "children"), ], [ - Input("meta-store", "data"), - Input("df-store", "data"), + Input(ElementIds.ID_SELECT_META_STORE, "data"), + Input(ElementIds.ID_SELECT_DF_STORE, "data"), ], ) def enable_tabs_when_data_is_loaded(meta, data): @@ -279,15 +280,15 @@ def enable_tabs_when_data_is_loaded(meta, data): @callback( [ - Output("modal", "is_open"), - Output("url-store", "data"), + Output(ElementIds.MODAL, "is_open"), + Output(ElementIds.ID_SELECT_URL_STORE, "data"), ], [ - Input("modal-yes-button", "n_clicks"), - Input("tab-one-map", "clickData"), - Input("modal-close-button", "n_clicks"), + Input(ElementIds.MODAL_YES_BUTTON, "n_clicks"), + Input(ElementIds.TAB_ONE_MAP, "clickData"), + Input(ElementIds.MODAL_CLOSE_BUTTON, "n_clicks"), ], - [State("modal", "is_open")], + [State(ElementIds.MODAL, "is_open")], prevent_initial_call=True, ) def display_modal_when_data_clicked(_, click_map, __, is_open): @@ -302,10 +303,10 @@ def display_modal_when_data_clicked(_, click_map, __, is_open): @callback( [ - Output("modal-header", "children"), + Output(ElementIds.MODAL_HEADER, "children"), ], [ - Input("tab-one-map", "clickData"), + Input(ElementIds.TAB_ONE_MAP, "clickData"), ], prevent_initial_call=True, ) @@ -317,7 +318,7 @@ def change_text_modal(click_map): @callback( - Output("skeleton-graph-container", "children"), + Output(ElementIds.SKELETON_GRAPH_CONTAINER, "children"), Input("url", "pathname"), ) def plot_location_epw_files(pathname): @@ -369,7 +370,7 @@ def plot_location_epw_files(pathname): return ( dcc.Graph( - id="tab-one-map", + id=ElementIds.TAB_ONE_MAP, figure=fig, config=generate_chart_name("epw_location_select"), ), diff --git a/pages/summary.py b/pages/summary.py index 20547b8d..d06dceb6 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -12,6 +12,7 @@ from pages.lib.global_scheme import template, tight_margins, mapping_dictionary from pages.lib.template_graphs import violin from pages.lib.global_column_names import ColNames +from pages.lib.global_elementids import ElementIds from pages.lib.utils import ( generate_chart_name, generate_units, @@ -42,7 +43,7 @@ def layout(): @callback( - Output("tab-two-container", "children"), [Input("si-ip-radio-input", "value")] + Output(ElementIds.TABLE_TWO_CONTAINER, "children"), [Input(ElementIds.ID_SUMMARY_SI_IP_RADIO_INPUT, "value")] ) def update_layout(si_ip): if si_ip == UnitSystem.SI: @@ -54,19 +55,19 @@ def update_layout(si_ip): return html.Div( className="container-col", - id="tab2-sec1-container", + id=ElementIds.TAB2_SCE1_CONTAINER, children=[ dcc.Loading( type="circle", children=html.Div( className="container-col", - id="location-info", + id=ElementIds.LOCATION_INFO, style={"padding": "12px"}, ), ), dcc.Loading( type="circle", - children=html.Div(className="tab-two-section", id="world-map"), + children=html.Div(className="tab-two-section", id=ElementIds.WORLD_MAP), ), html.Div( children=title_with_tooltip( @@ -83,7 +84,7 @@ def update_layout(si_ip): dbc.Button( "Download EPW", color="primary", - id="download-epw-button", + id=ElementIds.DOWN_EPW_BUTTON, ), width="auto", ), @@ -91,14 +92,14 @@ def update_layout(si_ip): dbc.Button( "Download Clima dataframe", color="primary", - id="download-button", + id=ElementIds.DOWNLOAD_BUTTON, ), width="auto", ), dbc.Col( [ - dcc.Download(id="download-dataframe-csv"), - dcc.Download(id="download-epw"), + dcc.Download(id=ElementIds.DOWNLOAD_DATAFRAME_CSV), + dcc.Download(id=ElementIds.DOWNLOAD_EPW), ], width=1, ), @@ -116,7 +117,7 @@ def update_layout(si_ip): "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!", color="warning", is_open=False, - id="warning-cdd-higher-hdd", + id=ElementIds.WARNING_CDD_HIGHER_HDD, ), dbc.Row( [ @@ -128,7 +129,7 @@ def update_layout(si_ip): ), dbc.Col( dbc.Input( - id="input-hdd-set-point", + id=ElementIds.INPUT_HDD_SET_POINT, type="number", value=heating_setpoint, style={"width": "4rem"}, @@ -143,7 +144,7 @@ def update_layout(si_ip): ), dbc.Col( dbc.Input( - id="input-cdd-set-point", + id=ElementIds.INPUT_CDD_SET_POINT, type="number", value=cooling_setpoint, style={"width": "4rem"}, @@ -152,7 +153,7 @@ def update_layout(si_ip): ), dbc.Col( dbc.Button( - id="submit-set-points", + id=ElementIds.SUBMIT_SET_POINTS, children="Submit", color="primary", ), @@ -164,7 +165,7 @@ def update_layout(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id="degree-days-chart-wrapper"), + children=html.Div(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER), ), html.Div( children=title_with_link( @@ -174,12 +175,12 @@ def update_layout(si_ip): ), ), dbc.Row( - id="graph-container", + id=ElementIds.GRAPH_CONTAINER, children=[ - dbc.Col(id="temp-profile-graph", width=12, md=6, lg=3), - dbc.Col(id="humidity-profile-graph", width=12, md=6, lg=3), - dbc.Col(id="solar-radiation-graph", width=12, md=6, lg=3), - dbc.Col(id="wind-speed-graph", width=12, md=6, lg=3), + 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), ], ), ], @@ -198,13 +199,13 @@ def update_layout(si_ip): @callback( - Output("world-map", "children"), - Input("meta-store", "data"), + Output(ElementIds.WORLD_MAP, "children"), + Input(ElementIds.ID_SUMMARY_META_STORE, "data"), ) def update_map(meta): """Update the contents of tab two. Passing in the general info (df, meta).""" map_world = dcc.Graph( - id="gh_rad-profile-graph", + id=ElementIds.GH_RAD_PROFILE_GRAPH, config=generate_chart_name("map", meta), figure=world_map(meta), ) @@ -213,12 +214,12 @@ def update_map(meta): @callback( - Output("location-info", "children"), - Input("df-store", "modified_timestamp"), + Output(ElementIds.LOCATION_INFO, "children"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) def update_location_info(ts, df, meta, si_ip): @@ -303,20 +304,20 @@ def update_location_info(ts, df, meta, si_ip): @callback( [ - Output("degree-days-chart-wrapper", "children"), - Output("warning-cdd-higher-hdd", "is_open"), + Output(ElementIds.DEGREE_DAYS_CHART_WRAPPER, "children"), + Output(ElementIds.WARNING_CDD_HIGHER_HDD, "is_open"), ], [ - Input("df-store", "modified_timestamp"), - Input("submit-set-points", "n_clicks_timestamp"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.SUBMIT_SET_POINTS, "n_clicks_timestamp"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("input-hdd-set-point", "value"), - State("input-cdd-set-point", "value"), - State("submit-set-points", "n_clicks"), - State("si-ip-unit-store", "data"), + 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"), ], ) def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ip): @@ -413,21 +414,21 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ @callback( - Output("temp-profile-graph", "children"), + Output(ElementIds.TEMP_PROFILE_GRAPH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) def update_violin_tdb(ts, global_local, df, meta, si_ip): units = generate_units_degree(si_ip) return dcc.Graph( - id="tdb-profile-graph", + id=ElementIds.TDB_PROFILE_GRAPH, className="violin-container", config=generate_chart_name("DryBulbTemperature", meta, units), figure=violin(df, ColNames.DBT, global_local, si_ip), @@ -435,15 +436,15 @@ def update_violin_tdb(ts, global_local, df, meta, si_ip): @callback( - Output("wind-speed-graph", "children"), + Output(ElementIds.WIND_SPEED_GRAPH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_wind(ts, global_local, df, meta, si_ip): @@ -458,22 +459,22 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): @callback( - Output("humidity-profile-graph", "children"), + Output(ElementIds.HUMIDITY_PROFILE_GRAPH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_rh(ts, global_local, df, meta, si_ip): """Update the contents of tab two. Passing in the general info (df, meta).""" units = generate_units(si_ip) return dcc.Graph( - id="rh-profile-graph", + id=ElementIds.RH_PROFILE_GRAPH, className="violin-container", config=generate_chart_name("RelativeHumidity", meta, units), figure=violin(df, ColNames.RH, global_local, si_ip), @@ -481,22 +482,22 @@ def update_tab_rh(ts, global_local, df, meta, si_ip): @callback( - Output("solar-radiation-graph", "children"), + Output(ElementIds.SOLAR_RADIATION_GRAPH, "children"), [ - Input("df-store", "modified_timestamp"), - Input("global-local-radio-input", "value"), + Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_gh_rad(ts, global_local, df, meta, si_ip): """Update the contents of tab two. Passing in the general info (df, meta).""" units = generate_units(si_ip) return dcc.Graph( - id="gh_rad-profile-graph", + id=ElementIds.GH_RAD_PROFILE_GRAPH, className="violin-container", config=generate_chart_name("GlobalHorizontalRadiation", meta, units), figure=violin(df, ColNames.GLOB_HOR_RAD, global_local, si_ip), @@ -504,12 +505,12 @@ def update_tab_gh_rad(ts, global_local, df, meta, si_ip): @callback( - Output("download-dataframe-csv", "data"), - [Input("download-button", "n_clicks")], + Output(ElementIds.DOWNLOAD_DATAFRAME_CSV, "data"), + [Input(ElementIds.DOWNLOAD_BUTTON, "n_clicks")], [ - State("df-store", "data"), - State("meta-store", "data"), - State("si-ip-unit-store", "data"), + State(ElementIds.ID_SUMMARY_DF_STORE, "data"), + State(ElementIds.ID_SUMMARY_META_STORE, "data"), + State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], prevent_initial_call=True, ) @@ -530,9 +531,9 @@ def download_clima_dataframe(n_clicks, df, meta, si_ip): @callback( - Output("download-epw", "data"), - [Input("download-epw-button", "n_clicks")], - [State("meta-store", "data")], + Output(ElementIds.DOWNLOAD_EPW, "data"), + [Input(ElementIds.DOWN_EPW_BUTTON, "n_clicks")], + [State(ElementIds.ID_SUMMARY_META_STORE, "data")], prevent_initial_call=True, ) def download_epw(n_clicks, meta): diff --git a/pages/sun.py b/pages/sun.py index 7d35fae8..74e50e8f 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -1,5 +1,5 @@ from copy import deepcopy -from pages.lib.global_column_names import ElementIds +from pages.lib.global_elementids import ElementIds import dash import dash_bootstrap_components as dbc diff --git a/pages/t_rh.py b/pages/t_rh.py index 400c99f4..47341cba 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -3,7 +3,8 @@ 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 -from pages.lib.global_column_names import ElementIds, Type, ComponentProperty, ColNames +from pages.lib.global_column_names import ColNames +from pages.lib.global_elementids import ElementIds from pages.lib.utils import ( generate_chart_name, generate_units, @@ -37,7 +38,7 @@ def layout(): className="text-next-to-input", children=["Select a variable: "] ), dropdown( - id=ElementIds.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]], @@ -55,7 +56,7 @@ def layout(): ), ), dcc.Loading( - type=Type.CIRCLE, + type="circle", children=html.Div(id=ElementIds.YEARLY_CHART), ), html.Div( @@ -66,7 +67,7 @@ def layout(): ), ), dcc.Loading( - type=Type.CIRCLE, + type="circle", children=html.Div(id=ElementIds.DAILY), ), html.Div( @@ -77,7 +78,7 @@ def layout(): ), ), dcc.Loading( - type=Type.CIRCLE, + type="circle", children=html.Div(id=ElementIds.HEATMAP), ), html.Div( @@ -97,16 +98,16 @@ def layout(): @callback( - Output(ElementIds.YEARLY_CHART, ComponentProperty.CHILDREN), + Output(ElementIds.YEARLY_CHART, "children"), [ - Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, ComponentProperty.VALUE), - Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), + Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_T_RH_DROPDOWN, "value"), ], [ - State(ElementIds.DF_STORE, ComponentProperty.DATA), - State(ElementIds.META_STORE, ComponentProperty.DATA), - State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA), + State(ElementIds.ID_T_RH_DF_STORE, "data"), + State(ElementIds.ID_T_RH_META_STORE, "data"), + State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): @@ -129,16 +130,16 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): @callback( - Output(ElementIds.DAILY, ComponentProperty.CHILDREN), + Output(ElementIds.DAILY, "children"), [ - Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, ComponentProperty.VALUE), - Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), + Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_T_RH_DROPDOWN, "value"), ], [ - State(ElementIds.DF_STORE, ComponentProperty.DATA), - State(ElementIds.META_STORE, ComponentProperty.DATA), - State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA), + State(ElementIds.ID_T_RH_DF_STORE, "data"), + State(ElementIds.ID_T_RH_META_STORE, "data"), + State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) def update_daily(_, global_local, dd_value, df, meta, si_ip): @@ -167,16 +168,16 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): @callback( - [Output(ElementIds.HEATMAP, ComponentProperty.CHILDREN)], + [Output(ElementIds.HEATMAP, "children")], [ - Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), - Input(ElementIds.GLOBAL_LOCAL_RADIO_INPUT, ComponentProperty.VALUE), - Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), + Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_T_RH_DROPDOWN, "value"), ], [ - State(ElementIds.DF_STORE, ComponentProperty.DATA), - State(ElementIds.META_STORE, ComponentProperty.DATA), - State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA), + State(ElementIds.ID_T_RH_DF_STORE, "data"), + State(ElementIds.ID_T_RH_META_STORE, "data"), + State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) def update_heatmap(_, global_local, dd_value, df, meta, si_ip): @@ -206,12 +207,12 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): @callback( - Output(ElementIds.TABLE_TMP_HUM, ComponentProperty.CHILDREN), + Output(ElementIds.TABLE_TMP_HUM, "children"), [ - Input(ElementIds.DF_STORE, ComponentProperty.MODIFIED_TIMESTAMP), - Input(ElementIds.DROPDOWN, ComponentProperty.VALUE), + Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_T_RH_DROPDOWN, "value"), ], - [State(ElementIds.DF_STORE, ComponentProperty.DATA), State(ElementIds.SI_IP_UNIT_STORE, ComponentProperty.DATA)], + [State(ElementIds.ID_T_RH_DF_STORE, "data"), State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data")], ) def update_table(_, dd_value, df, si_ip): """Update the contents of tab three. Passing in general info (df, meta).""" diff --git a/pages/wind.py b/pages/wind.py index d123a0b3..6b8cda4a 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -1,7 +1,7 @@ import dash from dash import dcc, html from dash_extensions.enrich import Output, Input, State, callback -from pages.lib.global_column_names import ElementIds +from pages.lib.global_elementids import ElementIds from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import month_lst, container_row_center_full @@ -382,7 +382,7 @@ def update_annual_wind_rose(_, df, meta, si_ip): # General [ Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), - Input(ElementIds.ID_WINDGLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ State(ElementIds.ID_WIND_DF_STORE, "data"), @@ -406,7 +406,7 @@ def update_tab_wind_speed(_, global_local, df, meta, si_ip): Output(ElementIds.WIND_DIRECTION, "children"), # General [ - Input(ElementIds.ID_WINDGLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"), ], [ State(ElementIds.ID_WIND_DF_STORE, "data"), From 01f40457712f80df5223a8119762bc2c33d1cada Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Sun, 24 Aug 2025 20:34:13 +1000 Subject: [PATCH 030/163] Fix:Align development branch versions to avoid conflicts --- .bumpversion.cfg | 2 +- .github/workflows/cypress.yml | 10 +- .github/workflows/deploy.yml | 41 ++-- .github/workflows/python.yml | 25 ++- .pre-commit-config.yaml | 10 + Pipfile | 1 + Pipfile.lock | 359 ++++++++++++++++++++---------- assets/manifest.json | 2 +- docs/contributing/contributing.md | 11 + pages/lib/extract_df.py | 4 +- pages/lib/layout.py | 2 +- pages/outdoor.py | 12 + pages/wind.py | 2 +- tests/python/test_utils.py | 9 +- 14 files changed, 323 insertions(+), 167 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a862796a..c6fbd8fa 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.8.18 +current_version = 0.9.0 commit = True tag = True diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index 0a8d7a21..36aa2465 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -1,5 +1,11 @@ -name: Cypress 🌲 -on: [push, pull_request] +name: Cypress Testing 🌲 +on: + push: + branches: + - development + pull_request: + branches: + - development jobs: cypress: runs-on: ubuntu-latest diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e550f2d6..b33b1daa 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,46 +1,31 @@ # .github/workflows/deploy.yml name: Deploy 🚀 Clima to Google Cloud Run (☁🏃) + on: push: branches: - main + jobs: deploy: name: Deploying 🚀 Clima runs-on: ubuntu-latest - if: "contains(github.event.head_commit.message, 'bump version')" steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - - name: Setup python - uses: actions/setup-python@v4 + - id: auth + uses: google-github-actions/auth@v1 with: - python-version: '3.11' - - - name: Export gcloud related env variable - run: export CLOUDSDK_PYTHON="/usr/bin/python3" + credentials_json: ${{ secrets.GCP_SA_KEY_JSON }} - # Build and push image to Google Container Registry - - name: Setting up - uses: google-github-actions/setup-gcloud@v0 + - name: Set up Cloud SDK + uses: google-github-actions/setup-gcloud@v1 with: - version: '318.0.0' - service_account_key: ${{ secrets.GCP_SA_KEY_JSON }} - service_account_email: "federico.tartarini@bears-berkeley.sg" - project_id: clima-316917 + project_id: heat-stress-scale - - name: Building (🏗️) - run: |- + - name: Building (🏗️) and Deploying (🚀) + run: | gcloud builds submit \ - --tag us-docker.pkg.dev/clima-316917/gcr.io/clima - - # Setup gcloud CLI - - name: Deploy (🚀) - uses: google-github-actions/deploy-cloudrun@v1 - with: - service: clima - image: us-docker.pkg.dev/clima-316917/gcr.io/clima - region: us-central1 - credentials: ${{ secrets.GCP_SA_KEY_JSON }} - project_id: clima-316917 \ No newline at end of file + --project=clima-316917 \ + --substitutions=_REPO_NAME="clima",_PROJ_NAME="clima-316917",_IMG_NAME="main",_GCR="us.gcr.io",_REGION="us-central1",_MEMORY="4Gi",_CPU="2" \ No newline at end of file diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 919693a6..3f57c5d9 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -1,5 +1,11 @@ -name: Python 🐍 -on: [push, pull_request] +name: Python Testing 🐍 +on: + push: + branches: + - development + pull_request: + branches: + - development jobs: pytest: runs-on: ubuntu-latest @@ -17,13 +23,14 @@ jobs: pip install pipenv pipenv install --dev - - name: Test Clima + - name: Ruff Check run: |- - pipenv run python -m pytest + pipenv run ruff check . + + - name: Ruff Format + run: |- + pipenv run ruff format - - name: Run Black - # TODO: Add to dev dependencies: Adding it right now - # bumps other dependencies and the application won't run. + - name: Test Clima run: |- - pip install black - black . --check + pipenv run python -m pytest diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..8a2b92b7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,10 @@ +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + # Ruff version. + rev: v0.12.9 + hooks: + # Run the linter. + - id: ruff-check + args: [ --fix ] + # Run the formatter. + - id: ruff-format \ No newline at end of file diff --git a/Pipfile b/Pipfile index b1667a53..91288833 100644 --- a/Pipfile +++ b/Pipfile @@ -23,6 +23,7 @@ pytest = "*" bump2version = "*" black = "*" ruff = "*" +pre-commit = "*" [requires] python_version = "3.11" diff --git a/Pipfile.lock b/Pipfile.lock index beb16723..89f3c053 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f96acae67c176ca7533141c154725a6a5563f14bf40da515952c2ee9b02a74c8" + "sha256": "7214a1158f64483648ecff36a57b61b67020408e0f16770b77d7165a9d6f47e0" }, "pipfile-spec": 6, "requires": { @@ -34,109 +34,96 @@ }, "certifi": { "hashes": [ - "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", - "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" + "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", + "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" ], "markers": "python_version >= '3.7'", - "version": "==2025.7.14" + "version": "==2025.8.3" }, "charset-normalizer": { "hashes": [ - "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", - "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", - "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", - "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", - "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", - "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", - "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", - "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", - "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", - "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", - "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", - "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", - "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", - "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", - "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", - "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", - "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", - "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", - "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", - "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", - "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", - "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", - "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", - "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", - "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", - "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", - "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", - "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", - "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", - "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", - "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", - "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", - "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", - "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", - "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", - "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", - "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", - "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", - "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", - "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", - "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", - "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", - "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", - "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", - "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", - "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", - "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", - "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", - "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", - "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", - "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", - "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", - "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", - "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", - "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", - "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", - "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", - "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", - "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", - "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", - "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", - "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", - "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", - "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", - "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", - "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", - "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", - "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", - "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", - "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", - "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", - "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", - "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", - "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", - "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", - "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", - "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", - "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", - "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", - "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", - "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", - "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", - "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", - "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", - "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", - "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", - "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", - "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", - "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", - "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", - "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", - "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" + "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", + "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", + "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", + "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", + "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", + "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", + "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", + "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", + "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", + "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", + "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", + "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", + "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", + "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", + "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", + "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", + "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", + "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", + "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", + "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", + "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", + "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", + "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", + "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", + "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", + "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", + "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", + "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", + "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", + "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", + "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", + "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", + "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", + "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", + "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", + "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", + "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", + "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", + "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", + "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", + "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", + "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", + "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", + "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", + "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", + "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", + "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", + "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", + "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", + "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", + "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", + "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", + "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", + "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", + "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", + "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", + "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", + "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", + "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", + "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", + "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", + "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", + "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", + "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", + "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", + "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", + "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", + "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", + "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", + "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", + "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", + "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", + "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", + "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", + "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", + "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", + "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", + "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", + "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" ], "markers": "python_version >= '3.7'", - "version": "==3.4.2" + "version": "==3.4.3" }, "click": { "hashes": [ @@ -146,6 +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", @@ -286,7 +281,7 @@ "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" ], - "markers": "python_version >= '3.7'", + "markers": "python_version >= '3.9'", "version": "==8.7.0" }, "itsdangerous": { @@ -589,11 +584,11 @@ }, "retrying": { "hashes": [ - "sha256:4d206e0ed2aff5ef2f3cd867abb9511e9e8f31127c5aca20f1d5246e476903b0", - "sha256:d736050c1adfc0a71fa022d9198ee130b0e66be318678a3fdd8b1b8872dc0997" + "sha256:bbc004aeb542a74f3569aeddf42a2516efefcdaff90df0eb38fbfbf19f179f59", + "sha256:d102e75d53d8d30b88562d45361d6c6c934da06fab31bd81c0420acb97a8ba39" ], "markers": "python_version >= '3.6'", - "version": "==1.4.1" + "version": "==1.4.2" }, "scipy": { "hashes": [ @@ -731,6 +726,14 @@ "markers": "python_version >= '3.5'", "version": "==1.0.1" }, + "cfgv": { + "hashes": [ + "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", + "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, "cleanpy": { "hashes": [ "sha256:9ddfa7ce80dd888b597a8b0bfeea3b69567839b6f41b775a4f76f46914d5170e", @@ -748,6 +751,37 @@ "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" + }, + "distlib": { + "hashes": [ + "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", + "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d" + ], + "version": "==0.4.0" + }, + "filelock": { + "hashes": [ + "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", + "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d" + ], + "markers": "python_version >= '3.9'", + "version": "==3.19.1" + }, + "identify": { + "hashes": [ + "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", + "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32" + ], + "markers": "python_version >= '3.9'", + "version": "==2.6.13" + }, "iniconfig": { "hashes": [ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", @@ -764,6 +798,14 @@ "markers": "python_version >= '3.8'", "version": "==1.1.0" }, + "nodeenv": { + "hashes": [ + "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", + "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" + ], + "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": "==1.9.1" + }, "packaging": { "hashes": [ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", @@ -796,6 +838,15 @@ "markers": "python_version >= '3.9'", "version": "==1.6.0" }, + "pre-commit": { + "hashes": [ + "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", + "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==4.3.0" + }, "pygments": { "hashes": [ "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", @@ -813,30 +864,98 @@ "markers": "python_version >= '3.9'", "version": "==8.4.1" }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, "ruff": { "hashes": [ - "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", - "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", - "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", - "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", - "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", - "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", - "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", - "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", - "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", - "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", - "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", - "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", - "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", - "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", - "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", - "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", - "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", - "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8" + "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", + "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", + "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", + "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", + "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", + "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", + "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", + "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", + "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", + "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", + "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", + "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", + "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", + "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", + "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", + "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", + "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", + "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", + "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.12.7" + "version": "==0.12.10" + }, + "virtualenv": { + "hashes": [ + "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", + "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a" + ], + "markers": "python_version >= '3.8'", + "version": "==20.34.0" } } } diff --git a/assets/manifest.json b/assets/manifest.json index 40b8618d..591f5946 100644 --- a/assets/manifest.json +++ b/assets/manifest.json @@ -467,7 +467,7 @@ "orientation": "portrait", "background_color": "#ffffff", "display": "standalone", - "id": "0.8.18", + "id": "0.9.0", "description": "CBE Clima Tool: a free and open-source web application for climate analysis tailored to sustainable building design", "start_url": "/", "scope": "/", diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 6fc210af..0da8696c 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -81,6 +81,17 @@ We use Black.exe to format the code. Install Black: +We use ruff to enforce the code style and code formatting. You can run it with: + +```bash +pipenv run ruff check . +pipenv run ruff format . +``` + +To ensure that your code is formatted correctly, we have a pre-commit hook that will run ruff before every commit. +Hence, you will need to make that the code is formatted correctly before committing your changes otherwise the commit will fail. +More information about pre-commit hooks can be found [here](https://pre-commit.com/). + ```bash pipenv install black ``` diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index c4c9a3b0..087c67cf 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -6,6 +6,7 @@ from datetime import timedelta from urllib.request import Request, urlopen +import logging import numpy as np import pandas as pd import requests @@ -43,7 +44,8 @@ def get_data(source_url): req = Request(source_url, headers=headers) epw = urlopen(req).read().decode() return epw.split("\n") - except: + except Exception as e: + logging.error(f"Failed to fetch EPW data: {e}") return None diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 1edea71d..ea7488b7 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -75,7 +75,7 @@ def footer(): dmc.Group( [ dmc.Anchor( - "Version: 0.8.18", + "Version: 0.9.0", href="https://center-for-the-built-environment.gitbook.io/clima/version/changelog", underline=True, c="white", diff --git a/pages/outdoor.py b/pages/outdoor.py index eb2350f8..b9fbbe5a 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -222,6 +222,18 @@ def layout(): ], ) def update_outdoor_comfort_output(_, df): + """ + Find the column(s) with the highest number of zero values. + + Args: + _: Unused callback input. + df: DataFrame-like object containing UTCI category columns. + + Returns + ------- + str + Description of the best weather condition(s). + """ cols = [ "utci_noSun_Wind_categories", "utci_noSun_noWind_categories", diff --git a/pages/wind.py b/pages/wind.py index 6b8cda4a..9ef474ae 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -329,7 +329,7 @@ def layout(): children=[ html.Div( children=title_with_link( - text="Wind Rose", + text="Annual Wind Rose", id_button="wind-rose-label", doc_link=DocLinks.WIND_ROSE, ), diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index eb0c59bd..714d1068 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -5,14 +5,17 @@ from config import UnitSystem +import requests + +from pages.lib.utils import summary_table_tmp_rh_tab +from pages.lib.extract_df import get_data, create_df + + root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) if root_dir not in sys.path: sys.path.append(root_dir) -import requests -from pages.lib.utils import summary_table_tmp_rh_tab -from pages.lib.extract_df import get_data, create_df def save_epw_test(path_file): From 7828453b84a30b4d9932c6149383ce6237d550eb Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:10:28 +1000 Subject: [PATCH 031/163] fix: aligned development branch versions of 3 days ago to avoid conflicts. --- .github/workflows/deploy.yml | 2 +- .github/workflows/python.yml | 6 +++--- cloudbuild.yaml | 2 +- config.py | 16 ++++++++-------- docs/README.md | 4 +++- docs/contributing/contributing.md | 12 ++++++++++-- docs/contributing/run-project-locally.md | 2 +- docs/documentation/tabs-explained/README.md | 2 +- .../tabs-explained/natural-ventilation.md | 3 --- .../outdoor-comfort/utci-explained.md | 6 +++--- .../psychrometric-chart/README.md | 7 +++---- .../psychrometric-chart-explained.md | 6 +++--- .../tabs-explained/sun-and-cloud/README.md | 5 ++--- .../sun-and-cloud/cloud-coverage.md | 4 ++-- .../customizable-daily-and-hourly-maps.md | 5 ++--- .../README.md | 2 +- docs/documentation/tabs-explained/tab-home.md | 6 +++--- .../tabs-explained/tab-summary/README.md | 6 +++--- .../tab-summary/clima-dataframe.md | 18 +++++++++--------- .../tab-summary/climate-profiles-explained.md | 4 ++-- .../tab-summary/degree-days-explained.md | 4 ++-- .../wind/how-to-read-a-wind-rose.md | 2 +- pages/lib/layout.py | 2 +- pages/lib/template_graphs.py | 2 +- pages/not_found_404.py | 4 +--- pages/select.py | 7 +++---- pages/summary.py | 9 ++++++++- tests/python/test_utils.py | 8 -------- 28 files changed, 78 insertions(+), 78 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index b33b1daa..bc24325c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -15,7 +15,7 @@ jobs: uses: actions/checkout@v4 - id: auth - uses: google-github-actions/auth@v1 + uses: google-github-actions/auth@v2 with: credentials_json: ${{ secrets.GCP_SA_KEY_JSON }} diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 3f57c5d9..32f7e7c3 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -25,11 +25,11 @@ jobs: - name: Ruff Check run: |- - pipenv run ruff check . + pipenv run ruff check . --output-format=github - - name: Ruff Format + - name: Ruff Format (verify) run: |- - pipenv run ruff format + pipenv run ruff format --check . - name: Test Clima run: |- diff --git a/cloudbuild.yaml b/cloudbuild.yaml index a60de353..8eb6ac4a 100644 --- a/cloudbuild.yaml +++ b/cloudbuild.yaml @@ -12,4 +12,4 @@ steps: '--image', '$_GCR/$_PROJ_NAME/$_REPO_NAME/$_IMG_NAME', '--region', '$_REGION', '--memory', '$_MEMORY', - '--cpu', '$_CPU',] \ No newline at end of file + '--cpu', '$_CPU'] \ No newline at end of file diff --git a/config.py b/config.py index bc7c61e8..bc794545 100644 --- a/config.py +++ b/config.py @@ -61,21 +61,21 @@ class PageInfo: TEMP_RH_NAME = "Temperature and Humidity" TEMP_RH_ORDER = 2 SOLAR_RADIATION_NAME = "Solar Radiation" - SOLAR_RADIATION_ORDER = 2 + SOLAR_RADIATION_ORDER = 3 SUN_NAME = "Sun and Clouds" - SUN_ORDER = 3 + SUN_ORDER = 4 WIND_NAME = "Wind" - WIND_ORDER = 4 + WIND_ORDER = 5 PSYCHROMETRIC_NAME = "Psychrometric Chart" - PSYCHROMETRIC_ORDER = 5 + PSYCHROMETRIC_ORDER = 6 NATURAL_VENTILATION_NAME = "Natural Ventilation" - NATURAL_VENTILATION_ORDER = 6 + NATURAL_VENTILATION_ORDER = 7 UTCI_NAME = "Outdoor Comfort" - UTCI_ORDER = 7 + UTCI_ORDER = 8 EXPLORER_NAME = "Data Explorer" - EXPLORER_ORDER = 8 + EXPLORER_ORDER = 9 CHANGELOG_NAME = "Changelog" - CHANGELOG_ORDER = 9 + CHANGELOG_ORDER = 10 NOT_FOUND_NAME = "404" diff --git a/docs/README.md b/docs/README.md index 12986645..05ce6e22 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,7 +10,7 @@ The CBE Clima Tool is a web-based application built to support climate analysis The CBE Clima Tool is open source. We have released the source code on a [public repository](https://github.com/CenterForTheBuiltEnvironment/clima). We welcome contributions from the community ([more info here](contributing/contributing.md)). -### Video Tutorial +## Video Tutorial Learn more about the CBE Clima Tool by watching the following video. @@ -18,6 +18,8 @@ Learn more about the CBE Clima Tool by watching the following video. CBE Clima tool tutorial and overview {% endembed %} +[Watch on YouTube](https://www.youtube.com/watch?v=VJ_wOHadVdw) + ## Contributions This ongoing project results from the collaboration and contributions of the people listed below. diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 0da8696c..d005aa08 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -88,8 +88,16 @@ pipenv run ruff check . pipenv run ruff format . ``` -To ensure that your code is formatted correctly, we have a pre-commit hook that will run ruff before every commit. -Hence, you will need to make that the code is formatted correctly before committing your changes otherwise the commit will fail. +To ensure that the code is formatted correctly, we use a pre-commit hook that runs Ruff before every commit. +Run the following once to enable hooks in your local repo: + +```bash +pipenv run pre-commit install +# optional: run on all files +pipenv run pre-commit run --all-files +``` + +Hence, you will need to make sure that the code is formatted correctly before committing your changes; otherwise, the commit will fail. More information about pre-commit hooks can be found [here](https://pre-commit.com/). ```bash diff --git a/docs/contributing/run-project-locally.md b/docs/contributing/run-project-locally.md index fef988bd..e125cd23 100644 --- a/docs/contributing/run-project-locally.md +++ b/docs/contributing/run-project-locally.md @@ -27,7 +27,7 @@ This guide is for Mac OSX, Linux, or Windows. 2. **Create a virtual environment using pipenv and install dependencies:** ```text - pipenv install + pipenv install --dev ``` 3. **Run tool locally** diff --git a/docs/documentation/tabs-explained/README.md b/docs/documentation/tabs-explained/README.md index 12b13e7a..b10d8a35 100644 --- a/docs/documentation/tabs-explained/README.md +++ b/docs/documentation/tabs-explained/README.md @@ -8,7 +8,7 @@ description: >- The Clima app is organized in a series of tabs that allow the exploration of various topics. All the tabs other than "Select Weather File" are active after a weather file has been selected. -Although there is a logical sequence in the organization of the tabs, thy can be accessed in any order. +Although there is a logical sequence in the organization of the tabs, thy can be accessed in any order. The Followin section will explain the content and the usage of each tab. diff --git a/docs/documentation/tabs-explained/natural-ventilation.md b/docs/documentation/tabs-explained/natural-ventilation.md index f863de59..3d140408 100644 --- a/docs/documentation/tabs-explained/natural-ventilation.md +++ b/docs/documentation/tabs-explained/natural-ventilation.md @@ -32,6 +32,3 @@ Learn more about the Natural Ventilation tab by watching the following video. {% embed url="https://youtu.be/VJ_wOHadVdw?si=_cUoFQGyxJD7V85a&t=703" %} - - - diff --git a/docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md b/docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md index 08adc0d6..bb6df199 100644 --- a/docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md +++ b/docs/documentation/tabs-explained/outdoor-comfort/utci-explained.md @@ -1,6 +1,6 @@ # UTCI explained -The UTCI tab allows users to analyze outdoor thermal comfort for a combination of different meteorological conditions based on the presence or absence of sun and wind. +The UTCI tab allows users to analyze outdoor thermal comfort for a combination of different meteorological conditions based on the presence or absence of sun and wind. ![Logos highlighting the different scenarios which can be displayed in Clima](<../../../.gitbook/assets/UTCI 01-01.jpg>) @@ -9,7 +9,7 @@ Clima leverages the several models implemented in [Pythermalcomfort](https://pyt * The "[Solar gain on people](https://pythermalcomfort.readthedocs.io/en/latest/reference/pythermalcomfort.html#solar-gain-on-people)" calculates the solar gain to the human body, so the mean radiant temperature. To simulate a sunless situation, Clima considers the person surrounded by surfaces that shade him, all of which tend toward dry bulb temperature; * Wind data is obtained directly from the weather file. The windless situation sets the value at 0.5 m/s, which is the minimum value allowed by the UTCI model. -The UTCI can then be visualized for the entire year for the scenario chosen. +The UTCI can then be visualized for the entire year for the scenario chosen.

UTCI perceived temperature annual heatmap in the four conditions for Rome, ITA

@@ -17,4 +17,4 @@ The values are then converted into a scale assessing thermal stress, either beca

UTCI heat stress index heatmap in the four conditions for Rome, ITA

-The UTCI is a useful tool to design the outdoor space, to maximize the number of comfortable hours. The designer can influence two factors out of the four driving outdoor comfort: radiant temperature (i.e. exposure to the sun) and wind speed (i.e. exposure to the wind). +The UTCI is a useful tool to design the outdoor space, to maximize the number of comfortable hours. The designer can influence two factors out of the four driving outdoor comfort: radiant temperature (i.e. exposure to the sun) and wind speed (i.e. exposure to the wind). \ No newline at end of file diff --git a/docs/documentation/tabs-explained/psychrometric-chart/README.md b/docs/documentation/tabs-explained/psychrometric-chart/README.md index 1ef00ac0..a0817de6 100644 --- a/docs/documentation/tabs-explained/psychrometric-chart/README.md +++ b/docs/documentation/tabs-explained/psychrometric-chart/README.md @@ -1,12 +1,12 @@ # Psychrometric Chart -**Clima** allows the user to visualize all annual weather conditions on a [psychometric diagram.](psychrometric-chart-explained.md) +**Clima** allows the user to visualize all annual weather conditions on a [psychrometric diagram.](psychrometric-chart-explained.md) The default diagram allows the users to overlay the frequency with which weather conditions recur throughout the year.

Example: Frequency of climatic condition in the Psychrometric chart of New York, USA

-With the first choice in the drop-down list, "None", it is possible to view temperature conditions in the psychometric diagram over the entire year. The visualized dots have the same gradient with a transparency rate, they are not colored according to a legend. Multiplying them when overlaid provides a visualization of their frequency, so the most common conditions. +With the first choice in the drop-down list, "None", it is possible to view temperature conditions in the psychrometric diagram over the entire year. The visualized dots have the same gradient with a transparency rate, they are not colored according to a legend. Multiplying them when overlaid provides a visualization of their frequency, so the most common conditions.

Example: Psychrometric chart with climatic conditions of New York, USA

@@ -22,5 +22,4 @@ Moreover, data can be filtered by date, time, or one of the [Clima dataframe](.. Learn more about the Psychrometric tab by watching the following video. -{% embed url="https://youtu.be/VJ_wOHadVdw?si=iAcBQpq3IgCNY-H6&t=582" %} - +{% embed url="https://youtu.be/VJ_wOHadVdw?si=iAcBQpq3IgCNY-H6&t=582" %} \ No newline at end of file diff --git a/docs/documentation/tabs-explained/psychrometric-chart/psychrometric-chart-explained.md b/docs/documentation/tabs-explained/psychrometric-chart/psychrometric-chart-explained.md index 9defd6e1..28a3b285 100644 --- a/docs/documentation/tabs-explained/psychrometric-chart/psychrometric-chart-explained.md +++ b/docs/documentation/tabs-explained/psychrometric-chart/psychrometric-chart-explained.md @@ -2,7 +2,7 @@ A [psychrometric diagram](https://en.wikipedia.org/wiki/Psychrometrics) is a psychrometry tool used to understand the relationship between humidity and air temperature conditions. Through the use of the psychrometric diagram and appropriate calculations, it is possible to know the amount of heat or cooling needed to achieve the desired temperature and humidity. -The **Clima** psychometric diagram shows dry bulb temperature on the abscissae, specific humidity on the ordinates, and relative humidity as parametric curves inside the graph. +The **Clima** psychrometric diagram shows dry bulb temperature on the abscissae, specific humidity on the ordinates, and relative humidity as parametric curves inside the graph.

Temperature line in the Psychrometric diagram

@@ -14,7 +14,7 @@ All air conditions cannot go beyond the 100% saturation curve, which means that

Relative humidity curves in the Psychrometric diagram

-The simplest transformation to be analyzed on the psychometric diagram is the heating and cooling processes. The transition from the starting condition (1) to the final one (2) occurs horizontally, at constant humidity ratio values. The final condition (2) can be inspected as a function of the starting one. +The simplest transformation to be analyzed on the psychrometric diagram is the heating and cooling processes. The transition from the starting condition (1) to the final one (2) occurs horizontally, at constant humidity ratio values. The final condition (2) can be inspected as a function of the starting one.

Cooling and heating process

@@ -28,4 +28,4 @@ The main application of the psychrometric diagram is in the design of large all- The diagram is applied whenever the humidity of a particular environment needs to be studied, for reasons of thermal comfort or for the preservation of valuable objects, such as in museums. -

Stradivari Violin, stored under precise temperature and humidity conditions to prevent the valuable wood from warping. Source: Frammentirivista

+

Stradivari Violin, stored under precise temperature and humidity conditions to prevent the valuable wood from warping. Source: Frammentirivista

\ No newline at end of file diff --git a/docs/documentation/tabs-explained/sun-and-cloud/README.md b/docs/documentation/tabs-explained/sun-and-cloud/README.md index ff1f17f2..c54f5571 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/README.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/README.md @@ -23,7 +23,6 @@ This allows the user to identify climatic patterns in relation to the apparent s ### Video Tutorial -Learn more about the Sun and Cloud tab by watching the following video. - -{% embed url="https://youtu.be/VJ_wOHadVdw?si=mB2xNH57MWW_4CRR&t=447" %} +Learn more about the Sun and Clouds tab by watching the following video. +{% embed url="https://youtu.be/VJ_wOHadVdw?si=mB2xNH57MWW_4CRR&t=447" %} \ No newline at end of file diff --git a/docs/documentation/tabs-explained/sun-and-cloud/cloud-coverage.md b/docs/documentation/tabs-explained/sun-and-cloud/cloud-coverage.md index 3228741a..bc250e1a 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/cloud-coverage.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/cloud-coverage.md @@ -2,7 +2,7 @@ The cloud coverage diagram reports, for every month of the year the frequency of "clear", "cloudy" or "intermediate" conditions. -As the Cloud cover is reported in tenths of coverage (i.e. 0 is 0/10 covered. 10 is total coverage) for the purpose of this graph we have simplified the scale as per the table below. +As the Cloud cover is reported in tenths of coverage (i.e. 0 is 0/10 covered. 10 is total coverage) for the purpose of this graph we have simplified the scale as per the table below. | Categorization | Color | Tenth of coverage | | ----------------------- | ------------------------------------------------------------------------------- | ----------------- | @@ -18,4 +18,4 @@ As the Cloud cover is reported in tenths of coverage (i.e. 0 is 0/10 covered. 10 | Cloudy (ABOVE range) | | 9 | | Cloudy (ABOVE range) | | 10 | -

Example cloud coverage graph for San Francisco, USA

+

Example cloud coverage graph for San Francisco, USA

\ No newline at end of file diff --git a/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md b/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md index bdc56435..c1dfc256 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md @@ -8,7 +8,6 @@ The chart above shows the [scatter plot](https://en.wikipedia.org/wiki/Scatter\_

Example: Heat map of the hourly Global Horizontal radiation on all days of the year for San Francisco, USA

-[Heat maps](https://en.wikipedia.org/wiki/Heat\_map) allow the intensity of values to be perceived through color palettes. These graphs are very helpful in seeing how magnitudes vary throughout the year. - -

Examples of daily graphs with different variables (from top left to bottom right): global horizontal radiation, global horizontal illuminance, zenith luminance, opaque sky cover

+[Heat maps](https://en.wikipedia.org/wiki/Heat\_map) allow the intensity of values to be perceived through color palettes. These graphs are very helpful in seeing how magnitudes vary throughout the year. +

Examples of daily graphs with different variables (from top left to bottom right): global horizontal radiation, global horizontal illuminance, zenith luminance, opaque sky cover

\ No newline at end of file diff --git a/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md b/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md index d9f65b5a..5013530d 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/README.md @@ -11,4 +11,4 @@ Typical daily graphs showing the amount of energy gained from the sun have many * manage the **indirect solar gain** transfer into the building with a time shift, exploiting the thermal mass, heating thick walls or concrete floors, or designing special rooms adjacent to the main spaces that rely on convection to transfer the heat, such as sunroom or [Trombe wall](https://en.wikipedia.org/wiki/Trombe\_wall); * evaluating sustainable **renewable energy solutions** such as solar thermal or photovoltaic panels. - The integral of the curves in the graphs is the total energy (in Wh/m²), supplied by the sun. Be careful in considering the [different types of solar radiation.](global-diffuse-and-normal-solar-radiation-explained.md) +The integral of the curves in the graphs is the total energy (in Wh/m²), supplied by the sun. Be careful in considering the [different types of solar radiation.](global-diffuse-and-normal-solar-radiation-explained.md) \ No newline at end of file diff --git a/docs/documentation/tabs-explained/tab-home.md b/docs/documentation/tabs-explained/tab-home.md index c0e73350..a7c1106a 100644 --- a/docs/documentation/tabs-explained/tab-home.md +++ b/docs/documentation/tabs-explained/tab-home.md @@ -4,13 +4,13 @@ description: This page explains how a user can load an EPW file in the Clima too # Select Weather File -Users can either choose to analyse the climate of the locations displayed on the map or upload a custom EPW file. After loading an EPW file the user can then access the other tabs to generate dynamic visualisations of the data. +Users can either choose to analyse the climate of the locations displayed on the map or upload a custom EPW file. After loading an EPW file the user can then access the other tabs to generate dynamic visualisations of the data. -### Video Tutorial +## Video Tutorial Learn more about how to analyse the climate of a specific location and uploading your custom EPW file by watching the following video. {% embed url="https://youtu.be/VJ_wOHadVdw?si=SxvUzaI9rCNIFFs0&t=136" %} -How to select or upload a EPW file +How to select or upload an EPW file {% endembed %} diff --git a/docs/documentation/tabs-explained/tab-summary/README.md b/docs/documentation/tabs-explained/tab-summary/README.md index 0f251189..4d474c10 100644 --- a/docs/documentation/tabs-explained/tab-summary/README.md +++ b/docs/documentation/tabs-explained/tab-summary/README.md @@ -8,9 +8,9 @@ The bottom section of the page comprises the heating and cooling degree day char ![Tab summary top](../../../.gitbook/assets/clima-summary-bottom.png) -### Video Tutorial +## Video Tutorial Learn more about the Climate Summary tab by watching the following video. -{% embed url="https://youtu.be/VJ_wOHadVdw?si=H-93XRhh5Neuby_b&t=220" %} - + +{% embed url="https://youtu.be/VJ_wOHadVdw?si=H-93XRhh5Neuby_b&t=220" %} \ No newline at end of file diff --git a/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md b/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md index 11cc1d82..0806f435 100644 --- a/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md +++ b/docs/documentation/tabs-explained/tab-summary/clima-dataframe.md @@ -1,6 +1,6 @@ # Clima Dataframe -**Clima** calculates new variables and creates a new **dataframe** containing the variables already inside the original EPW files and other we calculate. Users can overlay all the variables on the [sun path](../sun-and-cloud/), on the [psychometric chart](../psychrometric-chart/), and on the customizable graphs in the [data explorer](../data-explorer.md). +**Clima** calculates new variables and creates a new **dataframe** containing the variables already inside the original EPW files and other we calculate. Users can overlay all the variables on the [sun path](../sun-and-cloud/), on the [psychrometric chart](../psychrometric-chart/), and on the customizable graphs in the [data explorer](../data-explorer.md). All the variables in the new Clima dataframe are listed below. @@ -13,15 +13,15 @@ All the variables in the new Clima dataframe are listed below. * [Horizontal Infrared Radiation ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-horizontal-infrared-radiation-intensity) * [Global Horizontal Radiation ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-global-horizontal-radiation) * [Direct Normal Radiation ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-direct-normal-radiation) -* [Diffuse Horizontal Radiation](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-diffuse-horizontal-radiation) -* [Global Horizontal Illuminance](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-global-horizontal-illuminance) -* [Direct Normal Illuminance](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-direct-normal-illuminance) +* [Diffuse Horizontal Radiation](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-diffuse-horizontal-radiation) +* [Global Horizontal Illuminance](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-global-horizontal-illuminance) +* [Direct Normal Illuminance](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-direct-normal-illuminance) * [Diffuse Horizontal Illuminance ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-diffuse-horizontal-illuminance) * [Zenith Luminance ](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-zenith-luminance) -* [Wind Direction](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-wind-direction) -* [Wind Speed](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-wind-speed) -* [Total Sky Cover](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-total-sky-cover) -* [Opaque Sky Cover](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-opaque-sky-cover) +* [Wind Direction](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-wind-direction) +* [Wind Speed](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-wind-speed) +* [Total Sky Cover](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-total-sky-cover) +* [Opaque Sky Cover](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-opaque-sky-cover) * [Visibility](https://bigladdersoftware.com/epx/docs/22-2/auxiliary-programs/energyplus-weather-file-epw-data-dictionary.html#field-visibility) * [UTCI, Universal Thermal Climate Index](../outdoor-comfort/utci-explained.md) * [Vapor partial pressure](https://en.wikipedia.org/wiki/Vapor\_pressure) @@ -29,4 +29,4 @@ All the variables in the new Clima dataframe are listed below. * [Wet-bulb temperature](https://en.wikipedia.org/wiki/Wet-bulb\_temperature) * [Elevation](https://en.wikipedia.org/wiki/Solar\_zenith\_angle) * [Azimuth](https://en.wikipedia.org/wiki/Solar\_azimuth\_angle) -* [Saturation pressure](https://en.wikipedia.org/wiki/Vapour\_pressure\_of\_water) +* [Saturation pressure](https://en.wikipedia.org/wiki/Vapour\_pressure\_of\_water) \ No newline at end of file diff --git a/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md b/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md index 6eb090b7..dc45d8e4 100644 --- a/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md +++ b/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md @@ -2,7 +2,7 @@ The Climate Profiles graph gives the user the opportunity to observe at a glance the distribution of the data in the EPW file for four key variables and their variation between day and night. -The Climate Profiles graph are [Violin Plots](https://en.wikipedia.org/wiki/Violin\_plot). They show the [probability density](https://en.wikipedia.org/wiki/Probability\_density\_function) of the data at different values, usually smoothed by a [kernel density estimator](https://en.wikipedia.org/wiki/Kernel\_density\_estimator). Wider sections of the violin plot represent a higher probability that members of the population will take on the given value; the skinnier sections represent a lower probability. +The Climate Profiles graph are [Violin Plots](https://en.wikipedia.org/wiki/Violin\_plot). They show the [probability density](https://en.wikipedia.org/wiki/Probability\_density\_function) of the data at different values, usually smoothed by a [kernel density estimator](https://en.wikipedia.org/wiki/Kernel\_density\_estimator). Wider sections of the violin plot represent a higher probability that members of the population will take on the given value; the skinnier sections represent a lower probability. On mouse hover, they display various statistical properties of the data: @@ -13,4 +13,4 @@ On mouse hover, they display various statistical properties of the data: * 1st quartile * 3rd quartile -![Climate Profiles for Jerusalem Center, ISRAEL](<../../../.gitbook/assets/image (2) (1) (1).png>) +![Climate Profiles for Jerusalem Center, ISRAEL](<../../../.gitbook/assets/image (2) (1) (1).png>) \ No newline at end of file diff --git a/docs/documentation/tabs-explained/tab-summary/degree-days-explained.md b/docs/documentation/tabs-explained/tab-summary/degree-days-explained.md index 21d70bc9..0d6d9d09 100644 --- a/docs/documentation/tabs-explained/tab-summary/degree-days-explained.md +++ b/docs/documentation/tabs-explained/tab-summary/degree-days-explained.md @@ -4,7 +4,7 @@ you might want to start understanding what degree days are here: {% embed url="https://en.wikipedia.org/wiki/Heating_degree_day" %} -**Degree days** are calculated as the _integral of the difference between the outside air temperature and a base temperature over time_. +**Degree days** are calculated as the _integral of the difference between the outside air temperature and a base temperature over time_. If you can define a **base temperature** (the outside temperature above which a building needs no heating or cooling) then you can use this to estimate degree days. The building requires heating if the outside air temperature falls below the heating base temperature, and the heating degree days accrue; if the outside air temperature rises above the cooling base temperature, the structure requires cooling, and the cooling degree days accumulate. @@ -12,4 +12,4 @@ The base temperature does not necessarily correspond to the desired building int ![example deegree days for New York, Downtown Manhattan, NY, USA](<../../../.gitbook/assets/image (3).png>) -![example deegree days for Palermo Boccadifalco Airport, ITALY](<../../../.gitbook/assets/image (1) (1).png>) +![example deegree days for Palermo Boccadifalco Airport, ITALY](<../../../.gitbook/assets/image (1) (1).png>) \ No newline at end of file diff --git a/docs/documentation/tabs-explained/wind/how-to-read-a-wind-rose.md b/docs/documentation/tabs-explained/wind/how-to-read-a-wind-rose.md index 617e30e0..e7838698 100644 --- a/docs/documentation/tabs-explained/wind/how-to-read-a-wind-rose.md +++ b/docs/documentation/tabs-explained/wind/how-to-read-a-wind-rose.md @@ -14,7 +14,7 @@ The length of each radius around the circle shows how often the wind blew from t

Frequency of wind intensity recurrence in the wind rose

-As most graphs in **Clima Tool**, the wind rose is strongly interactive. Clicking on the legend will hide or highlight the selected category. As such, it is easy to go from a wind rose showing all the wind directions and frequency to one that highlights only the selected speed range. This can be particularly useful to identify low-frequency, high-speed wind patterns. +As most graphs in **Clima Tool**, the wind rose is strongly interactive. Clicking on the legend will hide or highlight the selected category. As such, it is easy to go from a wind rose showing all the wind directions and frequency to one that highlights only the selected speed range. This can be particularly useful to identify low-frequency, high-speed wind patterns.

Left: wind rose showing all velocity ranges
Right: the same data can be easily filtered (by clicking on the legend) to show only direction and frequency of wind speeds above 10.7m/s

diff --git a/pages/lib/layout.py b/pages/lib/layout.py index ea7488b7..558e3745 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -14,7 +14,7 @@ def alert(): children=[ dbc.Toast( [ - "If you have a moment, help us improving Clima and take a ", + "If you have a moment, help us improve Clima and take a ", html.A( "quick user survey", href="https://forms.gle/k289zP3R92jdu14M7", diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index e5acbe95..a5b214fa 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -850,5 +850,5 @@ def catch(func, handle=lambda e: e, *args, **kwargs): # Handle category not in dictionary try: return func(*args, **kwargs) - except Exception: + except (KeyError, IndexError, TypeError): return 0 diff --git a/pages/not_found_404.py b/pages/not_found_404.py index d28a630e..ff3b389c 100644 --- a/pages/not_found_404.py +++ b/pages/not_found_404.py @@ -13,9 +13,7 @@ layout = [ dmc.Title("I could not find the page you are currently looking for", order=4), - dmc.Text( - "Please navigate the the home page by using the button below", className="mb-2" - ), + dmc.Text("Use the button below to return to the home page.", className="mb-2"), Lottie( options=dict( loop=True, diff --git a/pages/select.py b/pages/select.py index d0e27df9..ad1a3a32 100644 --- a/pages/select.py +++ b/pages/select.py @@ -191,8 +191,8 @@ def submitted_data( messages_alert["invalid_format"], "warning", ) - except Exception: - # print(e) + except (ValueError, IndexError, KeyError) as e: + print(f"Error parsing EPW file: {e}") return ( None, None, @@ -331,8 +331,7 @@ def plot_location_epw_files(pathname): df = json_normalize(data["features"]) df[["lon", "lat"]] = pd.DataFrame(df["geometry.coordinates"].tolist()) - df["lat"] += 0.005 - df["lat"] += 0.005 + df["lat"] += 0.010 df = df.rename(columns={"properties.epw": "Source"}) fig = px.scatter_mapbox( diff --git a/pages/summary.py b/pages/summary.py index d06dceb6..6df0112a 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -263,7 +263,14 @@ def update_location_info(ts, df, meta, si_ip): total_solar_rad_unit = "k" + mapping_dictionary["glob_hor_rad"][si_ip]["unit"] total_solar_rad = f"Annual cumulative horizontal solar radiation: {total_solar_rad_value} {total_solar_rad_unit}" - total_diffuse_rad = f"Percentage of diffuse horizontal solar radiation: {round(df['dif_hor_rad'].sum() / df['glob_hor_rad'].sum() * 100, 1)} %" + glob_sum = df["glob_hor_rad"].sum() + if glob_sum > 0: + diffuse_percentage = round(df["dif_hor_rad"].sum() / glob_sum * 100, 1) + else: + diffuse_percentage = 0 + total_diffuse_rad = ( + f"Percentage of diffuse horizontal solar radiation: {diffuse_percentage} %" + ) tmp_unit = mapping_dictionary[ColNames.DBT][si_ip]["unit"] average_yearly_tmp = ( f"Average yearly temperature: {df['DBT'].mean().round(1)} " + tmp_unit diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py index 714d1068..ab085c42 100644 --- a/tests/python/test_utils.py +++ b/tests/python/test_utils.py @@ -1,4 +1,3 @@ -import sys import os import pandas as pd @@ -11,13 +10,6 @@ from pages.lib.extract_df import get_data, create_df -root_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) -if root_dir not in sys.path: - sys.path.append(root_dir) - - - - def save_epw_test(path_file): test_url = "http://climate.onebuilding.org/WMO_Region_6_Europe/ITA_Italy/ER_Emilia-Romagna/ITA_ER_Bologna-Marconi.AP.161400_TMYx.2004-2018.zip" From c7f11c4915742a5a33995d5ba305c67803132762 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sun, 24 Aug 2025 22:10:30 +1000 Subject: [PATCH 032/163] fix: updated new column names --- pages/lib/global_column_names.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index 6babc9a7..c8c627d0 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -13,6 +13,9 @@ class ColNames(str, Enum): DBT = "DBT" # Dry Bulb Temperature DPT = "DPT" # Dew Point Temperature RH = "RH" # Relative Humidity + HI_RH = "hiRH" # High Relative Humidity + LO_RH = "loRH" # Low Relative Humidity + HR = "hr" P_ATM = "p_atm" # Atmospheric Pressure # ==================== Radiation-related column ==================== @@ -31,6 +34,8 @@ class ColNames(str, Enum): ZLUMI = "Zlumi" # Luminance WIND_DIR = "wind_dir" # Wind Direction WIND_SPEED = "wind_speed" # Wind Speed + WIND_SPEED_UTCI = "wind_speed_utci" # Wind Speed Utci + WIND_SPEED_UTCI_0 = "wind_speed_utci_0" # Wind Speed Utci 0 TOT_SKY_COVER = "tot_sky_cover" # Total Sky Cover OSKYCOVER = "Oskycover" # Opaque Sky Cover VIS = "Vis" # Visibility @@ -41,6 +46,22 @@ class ColNames(str, Enum): AsolOptD = "AsolOptD" # Aerosol Optical Depth SnowD = "SnowD" # Snow Depth DaySSnow = "DaySSnow" # Daily Snow + ELEVATION = "elevation" # Elevation + APPARENT_ELEVATION = "apparent_elevation" # Apparent Elevation + AZIMUTH = "azimuth" # Azimuth + MRT = "MRT" + DELTA_MRT = "delta_mrt" + UTCI_SUN_WIND = "utci_Sun_Wind" # Utci Sun Wind + UTCI_SUN_NO_WIND = "utci_Sun_noWind" # Utci Sun no Wind + UTCI_NO_SUN_WIND = "utci_noSun_Wind" # Utci no Sun Wind + UTCI_NO_SUN_NO_WIND = "utci_noSun_noWind" # Utci no Sun no Wind + ADAPTIVE_COMFORT = "adaptive_comfort" # Adaptive comfort + ADAPTIVE_CMF_80_LOW = "adaptive_cmf_80_low" # Adaptive comfort 80 low + ADAPTIVE_CMF_80_UP = "adaptive_cmf_80_up" # Adaptive comfort 80 up + ADAPTIVE_CMF_90_LOW = "adaptive_cmf_90_low" # Adaptive comfort 90 low + ADAPTIVE_CMF_90_UP = "adaptive_cmf_90_up" # Adaptive comfort 90 up + ADAPTIVE_CMF_RMT = "adaptive_cmf_rmt" # Adaptive comfort rmt + NV_ALLOWED = "nv_allowed" # ==================== Calculation column ==================== FAKE_YEAR = "fake_year" # Fake Year From a6acb0bb51ba04f41c79d4e6863fd0160f1b19e2 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sun, 24 Aug 2025 22:11:12 +1000 Subject: [PATCH 033/163] fix: replaced the string with new column names --- pages/explorer.py | 19 +++++---- pages/lib/charts_sun.py | 40 +++++++++--------- pages/lib/extract_df.py | 80 ++++++++++++++++++------------------ pages/lib/template_graphs.py | 8 ++-- pages/natural_ventilation.py | 12 +++--- pages/psy-chart.py | 43 +++++++++---------- pages/summary.py | 8 ++-- 7 files changed, 106 insertions(+), 104 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index 92377385..3e079ebc 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -13,6 +13,7 @@ three_var_graph, ) from pages.lib.global_elementids import ElementIds +from pages.lib.global_column_names import ColNames from pages.lib.global_scheme import ( fig_config, dropdown_names, @@ -233,7 +234,7 @@ def section_two_inputs(): dropdown( id=ElementIds.SEC2_VAR_DROPDOWN, options=explore_dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -334,7 +335,7 @@ def section_two_inputs(): dropdown( id=ElementIds.SEC2_DATA_FILTER_VAR, options=explore_dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -438,7 +439,7 @@ def section_three_inputs(): dropdown( id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, options=explore_dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -548,7 +549,7 @@ def section_three_inputs(): dropdown( id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, options=explore_dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -946,11 +947,11 @@ def update_table( ) filtered_df = df[ - (df["month"] >= start_month) - & (df["month"] <= end_month) - & (df["hour"] >= start_hour) - & (df["hour"] <= end_hour) + (df[ColNames.MONTH] >= start_month) + & (df[ColNames.MONTH] <= end_month) + & (df[ColNames.HOUR] >= start_hour) + & (df[ColNames.HOUR] <= end_hour) ] return summary_table_tmp_rh_tab( - filtered_df[["month", "hour", dd_value, "month_names"]], dd_value, si_ip + filtered_df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], dd_value, si_ip ) diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 103e11b4..805fd717 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -113,7 +113,7 @@ def polar_graph(df, meta, global_local, var, si_ip): latitude = float(meta["lat"]) longitude = float(meta["lon"]) time_zone = float(meta["time_zone"]) - solpos = df.loc[df["apparent_elevation"] > 0, :] + solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :] if var != "None": var_unit = mapping_dictionary[var][si_ip]["unit"] @@ -135,7 +135,7 @@ def polar_graph(df, meta, global_local, var, si_ip): ) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta - solpos = df.loc[df["apparent_elevation"] > 0, :] + solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :] if var == "None": var_color = "orange" @@ -167,7 +167,7 @@ def polar_graph(df, meta, global_local, var, si_ip): fig.add_trace( go.Scatterpolar( r=90 * np.cos(np.radians(90 - solpos["apparent_zenith"])), - theta=solpos["azimuth"], + theta=solpos[ColNames.AZIMUTH], mode="markers", marker_color="orange", marker_size=marker_size, @@ -177,8 +177,8 @@ def polar_graph(df, meta, global_local, var, si_ip): solpos[ColNames.DAY], solpos[ColNames.MONTH_NAMES], solpos[ColNames.HOUR], - solpos["elevation"], - solpos["azimuth"], + solpos[ColNames.ELEVATION], + solpos[ColNames.AZIMUTH], ), axis=-1, ), @@ -197,7 +197,7 @@ def polar_graph(df, meta, global_local, var, si_ip): fig.add_trace( go.Scatterpolar( r=90 * np.cos(np.radians(90 - solpos["apparent_zenith"])), - theta=solpos["azimuth"], + theta=solpos[ColNames.AZIMUTH], mode="markers", marker=dict( color=solpos[var], @@ -213,8 +213,8 @@ def polar_graph(df, meta, global_local, var, si_ip): solpos[ColNames.DAY], solpos[ColNames.MONTH_NAMES], solpos[ColNames.HOUR], - solpos["elevation"], - solpos["azimuth"], + solpos[ColNames.ELEVATION], + solpos[ColNames.AZIMUTH], solpos[var], ), axis=-1, @@ -241,7 +241,7 @@ def polar_graph(df, meta, global_local, var, si_ip): times = pd.date_range(date, date + pd.Timedelta("24h"), freq="5min", tz=tz) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos["apparent_elevation"] > 0, :] + solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] fig.add_trace( go.Scatterpolar( @@ -265,7 +265,7 @@ def polar_graph(df, meta, global_local, var, si_ip): times = pd.date_range(date, date + pd.Timedelta("24h"), freq="5min", tz=tz) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos["apparent_elevation"] > 0, :] + solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] fig.add_trace( go.Scatterpolar( @@ -345,8 +345,8 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): if var == "None": fig.add_trace( go.Scatter( - y=df["elevation"], - x=df["azimuth"], + y=df[ColNames.ELEVATION], + x=df[ColNames.AZIMUTH], mode="markers", marker_color="orange", marker_size=marker_size, @@ -356,8 +356,8 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): df[ColNames.DAY], df[ColNames.MONTH_NAMES], df[ColNames.HOUR], - df["elevation"], - df["azimuth"], + df[ColNames.ELEVATION], + df[ColNames.AZIMUTH], ), axis=-1, ), @@ -375,8 +375,8 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): else: fig.add_trace( go.Scatter( - y=df["elevation"], - x=df["azimuth"], + y=df[ColNames.ELEVATION], + x=df[ColNames.AZIMUTH], mode="markers", marker=dict( color=df[var], @@ -392,8 +392,8 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): df[ColNames.DAY], df[ColNames.MONTH_NAMES], df[ColNames.HOUR], - df["elevation"], - df["azimuth"], + df[ColNames.ELEVATION], + df[ColNames.AZIMUTH], df[var], ), axis=-1, @@ -421,7 +421,7 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos["apparent_elevation"] > 0, :] + solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] fig.add_trace( go.Scatter( @@ -445,7 +445,7 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos["apparent_elevation"] > 0, :] + solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] fig.add_trace( go.Scatter( diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 087c67cf..ce2df0fa 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -227,9 +227,9 @@ def create_df(lst, file_name): epw_df = pd.concat([epw_df, solar_position], axis=1) # Add in UTCI - sol_altitude = epw_df["elevation"].mask(epw_df["elevation"] <= 0, 0) + sol_altitude = epw_df[ColNames.ELEVATION].mask(epw_df[ColNames.ELEVATION] <= 0, 0) sharp = [45] * 8760 - sol_radiation_dir = epw_df["dir_nor_rad"] + sol_radiation_dir = epw_df[ColNames.DIR_NOR_RAD] sol_transmittance = [1] * 8760 # CHECK VALUE f_svv = [1] * 8760 # CHECK VALUE f_bes = [1] * 8760 # CHECK VALUE @@ -249,48 +249,48 @@ def create_df(lst, file_name): floor_reflectance, ) mrt_df = pd.DataFrame.from_records(mrt) - mrt_df["delta_mrt"] = mrt_df["delta_mrt"].mask(mrt_df["delta_mrt"] >= 70, 70) + mrt_df[ColNames.DELTA_MRT] = mrt_df[ColNames.DELTA_MRT].mask(mrt_df[ColNames.DELTA_MRT] >= 70, 70) mrt_df = mrt_df.set_index(epw_df.times) epw_df = epw_df.join(mrt_df) - epw_df["MRT"] = epw_df["delta_mrt"] + epw_df[ColNames.DBT] - epw_df["wind_speed_utci"] = epw_df[ColNames.WIND_SPEED] - epw_df["wind_speed_utci"] = epw_df["wind_speed_utci"].mask( - epw_df["wind_speed_utci"] >= 17, 16.9 + epw_df[ColNames.MRT] = epw_df[ColNames.DELTA_MRT] + epw_df[ColNames.DBT] + epw_df[ColNames.WIND_SPEED_UTCI] = epw_df[ColNames.WIND_SPEED] + epw_df[ColNames.WIND_SPEED_UTCI] = epw_df[ColNames.WIND_SPEED_UTCI].mask( + epw_df[ColNames.WIND_SPEED_UTCI] >= 17, 16.9 ) - epw_df["wind_speed_utci"] = epw_df["wind_speed_utci"].mask( - epw_df["wind_speed_utci"] <= 0.5, 0.6 + epw_df[ColNames.WIND_SPEED_UTCI] = epw_df[ColNames.WIND_SPEED_UTCI].mask( + epw_df[ColNames.WIND_SPEED_UTCI] <= 0.5, 0.6 ) - epw_df["wind_speed_utci_0"] = epw_df["wind_speed_utci"].mask( - epw_df["wind_speed_utci"] >= 0, 0.5 + epw_df[ColNames.WIND_SPEED_UTCI_0] = epw_df[ColNames.WIND_SPEED_UTCI].mask( + epw_df[ColNames.WIND_SPEED_UTCI] >= 0, 0.5 ) - epw_df["utci_noSun_Wind"] = utci( - epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df["wind_speed_utci"], epw_df[ColNames.RH] + epw_df[ColNames.UTCI_NO_SUN_WIND] = utci( + epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df[ColNames.WIND_SPEED_UTCI], epw_df[ColNames.RH] ) - epw_df["utci_noSun_noWind"] = utci( - epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df["wind_speed_utci_0"], epw_df[ColNames.RH] + epw_df[ColNames.UTCI_NO_SUN_NO_WIND] = utci( + epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df[ColNames.WIND_SPEED_UTCI_0], epw_df[ColNames.RH] ) - epw_df["utci_Sun_Wind"] = utci( - epw_df[ColNames.DBT], epw_df["MRT"], epw_df["wind_speed_utci"], epw_df[ColNames.RH] + epw_df[ColNames.UTCI_SUN_WIND] = utci( + epw_df[ColNames.DBT], epw_df[ColNames.MRT], epw_df[ColNames.WIND_SPEED_UTCI], epw_df[ColNames.RH] ) - epw_df["utci_Sun_noWind"] = utci( - epw_df[ColNames.DBT], epw_df["MRT"], epw_df["wind_speed_utci_0"], epw_df[ColNames.RH] + epw_df[ColNames.UTCI_SUN_NO_WIND] = utci( + epw_df[ColNames.DBT], epw_df[ColNames.MRT], epw_df[ColNames.WIND_SPEED_UTCI_0], epw_df[ColNames.RH] ) utci_bins = [-999, -40, -27, -13, 0, 9, 26, 32, 38, 46, 999] utci_labels = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] epw_df["utci_noSun_Wind_categories"] = pd.cut( - x=epw_df["utci_noSun_Wind"], bins=utci_bins, labels=utci_labels + x=epw_df[ColNames.UTCI_NO_SUN_WIND], bins=utci_bins, labels=utci_labels ) epw_df["utci_noSun_noWind_categories"] = pd.cut( - x=epw_df["utci_noSun_noWind"], bins=utci_bins, labels=utci_labels + x=epw_df[ColNames.UTCI_NO_SUN_NO_WIND], bins=utci_bins, labels=utci_labels ) epw_df["utci_Sun_Wind_categories"] = pd.cut( - x=epw_df["utci_Sun_Wind"], bins=utci_bins, labels=utci_labels + x=epw_df[ColNames.UTCI_SUN_WIND], bins=utci_bins, labels=utci_labels ) epw_df["utci_Sun_noWind_categories"] = pd.cut( - x=epw_df["utci_Sun_noWind"], bins=utci_bins, labels=utci_labels + x=epw_df[ColNames.UTCI_SUN_NO_WIND], bins=utci_bins, labels=utci_labels ) # Add psy values @@ -302,12 +302,12 @@ def create_df(lst, file_name): # calculate adaptive data dbt_day_ave = epw_df.groupby([ColNames.DOY])[ColNames.DBT].mean().to_list() n = 7 - epw_df["adaptive_comfort"] = np.nan - epw_df["adaptive_cmf_80_low"] = np.nan - epw_df["adaptive_cmf_80_up"] = np.nan - epw_df["adaptive_cmf_90_low"] = np.nan - epw_df["adaptive_cmf_90_up"] = np.nan - epw_df["adaptive_cmf_rmt"] = np.nan + epw_df[ColNames.ADAPTIVE_COMFORT] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_80_LOW] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_80_UP] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_90_LOW] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_90_UP] = np.nan + epw_df[ColNames.ADAPTIVE_CMF_RMT] = np.nan for day in epw_df.DOY.unique(): i = day - 1 if i < n: @@ -328,12 +328,12 @@ def create_df(lst, file_name): v=0.5, limit_inputs=False, ) - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_rmt"] = rmt - epw_df.loc[epw_df.DOY == day, "adaptive_comfort"] = r["tmp_cmf"] - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_80_low"] = r["tmp_cmf_80_low"] - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_80_up"] = r["tmp_cmf_80_up"] - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_90_low"] = r["tmp_cmf_90_low"] - epw_df.loc[epw_df.DOY == day, "adaptive_cmf_90_up"] = r["tmp_cmf_90_up"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_RMT] = rmt + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_COMFORT] = r["tmp_cmf"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_LOW] = r["tmp_cmf_80_low"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_UP] = r["tmp_cmf_80_up"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_LOW] = r["tmp_cmf_90_low"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_UP] = r["tmp_cmf_90_up"] return epw_df, location_info @@ -372,11 +372,11 @@ def enthalpy(df, name): def convert_data(df, mapping_json): - df["adaptive_comfort"] = df["adaptive_comfort"] * 1.8 + 32 - df["adaptive_cmf_80_low"] = df["adaptive_cmf_80_low"] * 1.8 + 32 - df["adaptive_cmf_80_up"] = df["adaptive_cmf_80_up"] * 1.8 + 32 - df["adaptive_cmf_90_low"] = df["adaptive_cmf_90_low"] * 1.8 + 32 - df["adaptive_cmf_90_up"] = df["adaptive_cmf_90_up"] * 1.8 + 32 + df[ColNames.ADAPTIVE_COMFORT] = df[ColNames.ADAPTIVE_COMFORT] * 1.8 + 32 + df[ColNames.ADAPTIVE_CMF_80_LOW] = df[ColNames.ADAPTIVE_CMF_80_LOW] * 1.8 + 32 + df[ColNames.ADAPTIVE_CMF_80_UP] = df[ColNames.ADAPTIVE_CMF_80_UP] * 1.8 + 32 + df[ColNames.ADAPTIVE_CMF_90_LOW] = df[ColNames.ADAPTIVE_CMF_90_LOW] * 1.8 + 32 + df[ColNames.ADAPTIVE_CMF_90_UP] = df[ColNames.ADAPTIVE_CMF_90_UP] * 1.8 + 32 mapping_dict = json.loads(mapping_json) for key in json.loads(mapping_json): diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index a5b214fa..ae840591 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -187,13 +187,13 @@ def yearly_profile(df, var, global_local, si_ip): # plot relative Humidity limits (30-70%) lo_rh = [30] * 365 hi_rh = [70] * 365 - lo_rh_df = pd.DataFrame({"loRH": lo_rh}) - hi_rh_df = pd.DataFrame({"hiRH": hi_rh}) + lo_rh_df = pd.DataFrame({ColNames.LO_RH: lo_rh}) + hi_rh_df = pd.DataFrame({ColNames.HI_RH: hi_rh}) trace3 = go.Bar( x=df[ColNames.UTC_TIME].dt.date.unique(), - y=hi_rh_df["hiRH"] - lo_rh_df["loRH"], - base=lo_rh_df["loRH"], + y=hi_rh_df[ColNames.HI_RH] - lo_rh_df[ColNames.LO_RH], + base=lo_rh_df[ColNames.LO_RH], name="humidity comfort band", marker_opacity=0.3, marker_color="silver", diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index c772c0b4..8ee2fcf4 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -511,7 +511,7 @@ def nv_bar_chart( color_in = "dodgerblue" - df["nv_allowed"] = 1 + df[ColNames.NV_ALLOWED] = 1 df = filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, "nv_allowed" @@ -519,18 +519,18 @@ def nv_bar_chart( # this should be the total after filtering by time tot_month_hours = ( - df.groupby(df[ColNames.UTC_TIME].dt.month)["nv_allowed"].sum().values + df.groupby(df[ColNames.UTC_TIME].dt.month)[ColNames.NV_ALLOWED].sum().values ) if dbt_data_filter and (min_dbt_val <= max_dbt_val): - df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), "nv_allowed"] = 0 + df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), ColNames.NV_ALLOWED] = 0 if dpt_data_filter: - df.loc[(df[filter_var] > max_dpt_val), "nv_allowed"] = 0 + df.loc[(df[filter_var] > max_dpt_val), ColNames.NV_ALLOWED] = 0 n_hours_nv_allowed = ( - df.dropna(subset="nv_allowed") - .groupby(df[ColNames.UTC_TIME].dt.month)["nv_allowed"] + df.dropna(subset=ColNames.NV_ALLOWED) + .groupby(df[ColNames.UTC_TIME].dt.month)[ColNames.NV_ALLOWED] .sum() .values ) diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 51bac4cc..8d22822a 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -13,6 +13,7 @@ from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.global_elementids import ElementIds +from pages.lib.global_column_names import ColNames from pages.lib.global_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -174,7 +175,7 @@ def inputs(): dropdown( id=ElementIds.PSY_VAR_DROPDOWN , options=dropdown_names, - value="RH", + value=ColNames.RH, style={"flex": "70%"}, ), ], @@ -313,17 +314,17 @@ def update_psych_chart( if global_local == "global": # Set Global values for Max and minimum - var_range_x = mapping_dictionary["DBT"][si_ip]["range"] - var_range_y = mapping_dictionary["hr"][si_ip]["range"] + var_range_x = mapping_dictionary[ColNames.DBT][si_ip]["range"] + var_range_y = mapping_dictionary[ColNames.HR][si_ip]["range"] else: # Set maximum and minimum according to data - data_max = 5 * ceil(df["DBT"].max() / 5) - data_min = 5 * floor(df["DBT"].min() / 5) + data_max = 5 * ceil(df[ColNames.DBT].max() / 5) + data_min = 5 * floor(df[ColNames.DBT].min() / 5) var_range_x = [data_min, data_max] - data_max = round(df["hr"].max(), 4) - data_min = round(df["hr"].min(), 4) + data_max = round(df[ColNames.HR].max(), 4) + data_min = round(df[ColNames.HR].min(), 4) var_range_y = [data_min * 1000, data_max * 1000] title = "Psychrometric Chart" @@ -339,7 +340,7 @@ def update_psych_chart( hr_list = np.vectorize(psy.psy_ta_rh)(dbt_list, rh) hr_df = pd.DataFrame.from_records(hr_list) name = "rh" + str(rh) - rh_df[name] = hr_df["hr"] + rh_df[name] = hr_df[ColNames.HR] fig = go.Figure() @@ -369,13 +370,13 @@ def update_psych_chart( ) ) - df_hr_multiply = list(df["hr"]) + df_hr_multiply = list(df[ColNames.HR]) for k in range(len(df_hr_multiply)): df_hr_multiply[k] = df_hr_multiply[k] * 1000 if var == "None": fig.add_trace( go.Scatter( - x=df["DBT"], + x=df[ColNames.DBT], y=df_hr_multiply, showlegend=False, mode="markers", @@ -385,16 +386,16 @@ def update_psych_chart( showscale=False, opacity=0.2, ), - hovertemplate=mapping_dictionary["DBT"]["name"] + hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + ": %{x:.2f}" - + mapping_dictionary["DBT"]["name"], + + mapping_dictionary[ColNames.DBT]["name"], name="", ) ) elif var == "Frequency": fig.add_trace( go.Histogram2d( - x=df["DBT"], + x=df[ColNames.DBT], y=df_hr_multiply, name="", colorscale=var_color, @@ -438,7 +439,7 @@ def update_psych_chart( fig.add_trace( go.Scatter( - x=df["DBT"], + x=df[ColNames.DBT], y=df_hr_multiply, showlegend=False, mode="markers", @@ -450,14 +451,14 @@ def update_psych_chart( colorscale=var_color, colorbar=var_colorbar, ), - customdata=np.stack((df["RH"], df["h"], df[var], df["t_dp"]), axis=-1), - hovertemplate=mapping_dictionary["DBT"]["name"] + customdata=np.stack((df[ColNames.RH], df["h"], df[var], df["t_dp"]), axis=-1), + hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + ": %{x:.2f}" - + mapping_dictionary["DBT"][si_ip]["unit"] + + mapping_dictionary[ColNames.DBT][si_ip]["unit"] + "
" - + mapping_dictionary["RH"]["name"] + + mapping_dictionary[ColNames.RH]["name"] + ": %{customdata[0]:.2f}" - + mapping_dictionary["RH"][si_ip]["unit"] + + mapping_dictionary[ColNames.RH][si_ip]["unit"] + "
" + mapping_dictionary["h"]["name"] + ": %{customdata[1]:.2f}" @@ -475,8 +476,8 @@ def update_psych_chart( ) ) - xtitle_name = "Temperature" + " " + mapping_dictionary["DBT"][si_ip]["unit"] - ytitle_name = "Humidity Ratio" + " " + mapping_dictionary["hr"][si_ip]["unit"] + xtitle_name = "Temperature" + " " + mapping_dictionary[ColNames.DBT][si_ip]["unit"] + ytitle_name = "Humidity Ratio" + " " + mapping_dictionary[ColNames.HR][si_ip]["unit"] fig.update_layout(template=template, margin=tight_margins) fig.update_xaxes( title_text=xtitle_name, diff --git a/pages/summary.py b/pages/summary.py index 6df0112a..e5509eee 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -259,13 +259,13 @@ def update_location_info(ts, df, meta, si_ip): # 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["glob_hor_rad"].sum() / 1000, 2) - total_solar_rad_unit = "k" + mapping_dictionary["glob_hor_rad"][si_ip]["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]["unit"] total_solar_rad = f"Annual cumulative horizontal solar radiation: {total_solar_rad_value} {total_solar_rad_unit}" - glob_sum = df["glob_hor_rad"].sum() + glob_sum = df[ColNames.GLOB_HOR_RAD].sum() if glob_sum > 0: - diffuse_percentage = round(df["dif_hor_rad"].sum() / glob_sum * 100, 1) + diffuse_percentage = round(df[ColNames.DIF_HOR_RAD].sum() / glob_sum * 100, 1) else: diffuse_percentage = 0 total_diffuse_rad = ( From e85bfe1cdcdbd5cb33321c918b348c872305f9ea Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sun, 24 Aug 2025 22:12:05 +1000 Subject: [PATCH 034/163] fix: renamed the python file of global_element_ids.py --- pages/explorer.py | 2 +- pages/lib/{global_elementids.py => global_element_ids.py} | 0 pages/natural_ventilation.py | 2 +- pages/outdoor.py | 2 +- pages/psy-chart.py | 2 +- pages/select.py | 2 +- pages/summary.py | 2 +- pages/sun.py | 2 +- pages/t_rh.py | 2 +- pages/wind.py | 2 +- 10 files changed, 9 insertions(+), 9 deletions(-) rename pages/lib/{global_elementids.py => global_element_ids.py} (100%) diff --git a/pages/explorer.py b/pages/explorer.py index 3e079ebc..32ae86db 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -12,7 +12,7 @@ two_var_graph, three_var_graph, ) -from pages.lib.global_elementids import ElementIds +from pages.lib.global_element_ids import ElementIds from pages.lib.global_column_names import ColNames from pages.lib.global_scheme import ( fig_config, diff --git a/pages/lib/global_elementids.py b/pages/lib/global_element_ids.py similarity index 100% rename from pages/lib/global_elementids.py rename to pages/lib/global_element_ids.py diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 8ee2fcf4..091d8fa4 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -20,7 +20,7 @@ ) from pages.lib.template_graphs import filter_df_by_month_and_hour from pages.lib.global_column_names import ColNames -from pages.lib.global_elementids import ElementIds +from pages.lib.global_element_ids import ElementIds from pages.lib.utils import ( title_with_tooltip, generate_chart_name, diff --git a/pages/outdoor.py b/pages/outdoor.py index b9fbbe5a..33d1fd5c 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -6,7 +6,7 @@ import numpy as np from config import PageUrls, DocLinks, PageInfo -from pages.lib.global_elementids import ElementIds +from pages.lib.global_element_ids import ElementIds from pages.lib.global_scheme import ( outdoor_dropdown_names, ) diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 8d22822a..7fbd802d 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -12,7 +12,7 @@ from pythermalcomfort import psychrometrics as psy from config import PageUrls, DocLinks, PageInfo, UnitSystem -from pages.lib.global_elementids import ElementIds +from pages.lib.global_element_ids import ElementIds from pages.lib.global_column_names import ColNames from pages.lib.global_scheme import ( container_row_center_full, diff --git a/pages/select.py b/pages/select.py index ad1a3a32..89fd67f0 100644 --- a/pages/select.py +++ b/pages/select.py @@ -13,7 +13,7 @@ from pages.lib.extract_df import convert_data from pages.lib.extract_df import create_df, get_data, get_location_info -from pages.lib.global_elementids import ElementIds +from pages.lib.global_element_ids import ElementIds from pages.lib.global_scheme import mapping_dictionary from config import PageUrls, PageInfo, UnitSystem from pages.lib.utils import generate_chart_name diff --git a/pages/summary.py b/pages/summary.py index e5509eee..cce46c88 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -12,7 +12,7 @@ from pages.lib.global_scheme import template, tight_margins, mapping_dictionary from pages.lib.template_graphs import violin from pages.lib.global_column_names import ColNames -from pages.lib.global_elementids import ElementIds +from pages.lib.global_element_ids import ElementIds from pages.lib.utils import ( generate_chart_name, generate_units, diff --git a/pages/sun.py b/pages/sun.py index 74e50e8f..b6663e76 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -1,5 +1,5 @@ from copy import deepcopy -from pages.lib.global_elementids import ElementIds +from pages.lib.global_element_ids import ElementIds import dash import dash_bootstrap_components as dbc diff --git a/pages/t_rh.py b/pages/t_rh.py index 47341cba..12d89fba 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -4,7 +4,7 @@ from pages.lib.global_scheme import dropdown_names from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile from pages.lib.global_column_names import ColNames -from pages.lib.global_elementids import ElementIds +from pages.lib.global_element_ids import ElementIds from pages.lib.utils import ( generate_chart_name, generate_units, diff --git a/pages/wind.py b/pages/wind.py index 9ef474ae..8517f510 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -1,7 +1,7 @@ import dash from dash import dcc, html from dash_extensions.enrich import Output, Input, State, callback -from pages.lib.global_elementids import ElementIds +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 2dd992c646ad99cad9bdeda9c9aba82609a7c260 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sun, 24 Aug 2025 23:50:58 +1000 Subject: [PATCH 035/163] fix: resolved the format to avoid conflict --- .github/workflows/deploy.yml | 4 ++-- Pipfile.lock | 2 +- pages/outdoor.py | 18 +++++++++--------- pages/summary.py | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bc24325c..5763701d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,5 +27,5 @@ jobs: - name: Building (🏗️) and Deploying (🚀) run: | gcloud builds submit \ - --project=clima-316917 \ - --substitutions=_REPO_NAME="clima",_PROJ_NAME="clima-316917",_IMG_NAME="main",_GCR="us.gcr.io",_REGION="us-central1",_MEMORY="4Gi",_CPU="2" \ No newline at end of file + --project=clima-316917 \ + --substitutions=_REPO_NAME="clima",_PROJ_NAME="clima-316917",_IMG_NAME="main",_GCR="us.gcr.io",_REGION="us-central1",_MEMORY="4Gi",_CPU="2" \ No newline at end of file diff --git a/Pipfile.lock b/Pipfile.lock index 89f3c053..ef31819c 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -947,7 +947,7 @@ ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.12.10" + "version": "==0.12.9" }, "virtualenv": { "hashes": [ diff --git a/pages/outdoor.py b/pages/outdoor.py index 33d1fd5c..0e8d4cef 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -223,17 +223,17 @@ def layout(): ) def update_outdoor_comfort_output(_, df): """ - Find the column(s) with the highest number of zero values. + Find the column(s) with the highest number of zero values. - Args: - _: Unused callback input. - df: DataFrame-like object containing UTCI category columns. + Args: + _: Unused callback input. + df: DataFrame-like object containing UTCI category columns. - Returns - ------- - str - Description of the best weather condition(s). - """ + Returns + ------- + str + Description of the best weather condition(s). + """ cols = [ "utci_noSun_Wind_categories", "utci_noSun_noWind_categories", diff --git a/pages/summary.py b/pages/summary.py index cce46c88..396f926d 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -273,14 +273,14 @@ def update_location_info(ts, df, meta, si_ip): ) tmp_unit = mapping_dictionary[ColNames.DBT][si_ip]["unit"] average_yearly_tmp = ( - f"Average yearly temperature: {df['DBT'].mean().round(1)} " + tmp_unit + f"Average yearly temperature: {df[ColNames.DBT].mean().round(1)} " + tmp_unit ) hottest_yearly_tmp = ( - f"Hottest yearly temperature (99%): {df['DBT'].quantile(0.99).round(1)} " + f"Hottest yearly temperature (99%): {df[ColNames.DBT].quantile(0.99).round(1)} " + tmp_unit ) coldest_yearly_tmp = ( - f"Coldest yearly temperature (1%): {df['DBT'].quantile(0.01).round(1)} " + f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} " + tmp_unit ) From 2381e85b1e3a7f4fe9317dc1f4aa7a4d26f466ce Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Mon, 25 Aug 2025 00:21:33 +1000 Subject: [PATCH 036/163] Fix: updated the file global_element_ids.py and summary.py --- pages/lib/global_element_ids.py | 4 +++- pages/summary.py | 27 ++++++++++----------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py index 6fcce955..d36ff4f7 100644 --- a/pages/lib/global_element_ids.py +++ b/pages/lib/global_element_ids.py @@ -172,7 +172,7 @@ class ElementIds(str, Enum): ID_SELECT_SI_IP_UNIT_STORE = "si-ip-unit-store" ID_SELECT_SI_IP_RADIO_INPUT = "si-ip-radio-input" BANNER_SUBTITLE = "banner-subtitle" - TABLE_TWO_CONTAINER = "tab-two-container" + TAB_TWO_CONTAINER = "tab-two-container" ID_SUMMARY_SI_IP_RADIO_INPUT = "si-ip-radio-input" TAB2_SCE1_CONTAINER = "tab2-sec1-container" LOCATION_INFO = "location-info" @@ -187,6 +187,7 @@ class ElementIds(str, Enum): SUBMIT_SET_POINTS = "submit-set-points" GRAPH_CONTAINER = "graph-container" DEGREE_DAYS_CHART_WRAPPER = "degree-days-chart-wrapper" + DEGREE_DAYS_CHART = "degree-days-chart" TEMP_PROFILE_GRAPH = "temp-profile-graph" HUMIDITY_PROFILE_GRAPH = "humidity-profile-graph" SOLAR_RADIATION_GRAPH = "solar-radiation-graph" @@ -196,5 +197,6 @@ class ElementIds(str, Enum): ID_SUMMARY_DF_STORE = "df-store" ID_SUMMARY_SI_IP_UNIT_STORE = "si-ip-unit-store" ID_SUMMARY_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" + WIND_PROFILE_GRAPH = "wind-profile-graph" TDB_PROFILE_GRAPH = "tdb-profile-graph" RH_PROFILE_GRAPH = "rh-profile-graph" diff --git a/pages/summary.py b/pages/summary.py index 396f926d..07235bad 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -35,7 +35,7 @@ def layout(): return html.Div( className="container-col", - id="tab-two-container", + id=ElementIds.TAB_TWO_CONTAINER, children=[ # ], @@ -43,7 +43,7 @@ def layout(): @callback( - Output(ElementIds.TABLE_TWO_CONTAINER, "children"), [Input(ElementIds.ID_SUMMARY_SI_IP_RADIO_INPUT, "value")] + Output(ElementIds.TAB_TWO_CONTAINER, "children"), [Input(ElementIds.ID_SUMMARY_SI_IP_RADIO_INPUT, "value")] ) def update_layout(si_ip): if si_ip == UnitSystem.SI: @@ -259,28 +259,21 @@ def update_location_info(ts, df, meta, si_ip): # 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]["unit"] + total_solar_rad_value = round(df["glob_hor_rad"].sum() / 1000, 2) + total_solar_rad_unit = "k" + mapping_dictionary["glob_hor_rad"][si_ip]["unit"] 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 - total_diffuse_rad = ( - f"Percentage of diffuse horizontal solar radiation: {diffuse_percentage} %" - ) + total_diffuse_rad = f"Percentage of diffuse horizontal solar radiation: {round(df['dif_hor_rad'].sum() / df['glob_hor_rad'].sum() * 100, 1)} %" tmp_unit = mapping_dictionary[ColNames.DBT][si_ip]["unit"] average_yearly_tmp = ( - f"Average yearly temperature: {df[ColNames.DBT].mean().round(1)} " + tmp_unit + f"Average yearly temperature: {df['DBT'].mean().round(1)} " + tmp_unit ) hottest_yearly_tmp = ( - f"Hottest yearly temperature (99%): {df[ColNames.DBT].quantile(0.99).round(1)} " + f"Hottest yearly temperature (99%): {df['DBT'].quantile(0.99).round(1)} " + tmp_unit ) coldest_yearly_tmp = ( - f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} " + f"Coldest yearly temperature (1%): {df['DBT'].quantile(0.01).round(1)} " + tmp_unit ) @@ -412,7 +405,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ units = generate_units_degree(si_ip) chart = dcc.Graph( - id="degree-days-chart", + id=ElementIds.DEGREE_DAYS_CHART, config=generate_chart_name("hdd_cdd", meta, custom_inputs, units), figure=fig, ) @@ -458,7 +451,7 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): """Update the contents of tab two. Passing in the general info (df, meta).""" units = generate_units(si_ip) return dcc.Graph( - id="wind-profile-graph", + id=ElementIds.WIND_PROFILE_GRAPH, className="violin-container", config=generate_chart_name("WindSpeed", meta, units), figure=violin(df, ColNames.WIND_SPEED, global_local, si_ip), From f822ccacb5b92dd86c3f43d05cfffdb3a6d09b54 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Mon, 25 Aug 2025 10:46:08 +1000 Subject: [PATCH 037/163] fix: replaced the string with global constants --- pages/summary.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pages/summary.py b/pages/summary.py index 07235bad..2230d3a8 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -259,21 +259,21 @@ def update_location_info(ts, df, meta, si_ip): # 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["glob_hor_rad"].sum() / 1000, 2) - total_solar_rad_unit = "k" + mapping_dictionary["glob_hor_rad"][si_ip]["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]["unit"] total_solar_rad = f"Annual cumulative horizontal solar radiation: {total_solar_rad_value} {total_solar_rad_unit}" - total_diffuse_rad = f"Percentage of diffuse horizontal solar radiation: {round(df['dif_hor_rad'].sum() / df['glob_hor_rad'].sum() * 100, 1)} %" + total_diffuse_rad = f"Percentage of diffuse horizontal solar radiation: {round(df[ColNames.DIF_HOR_RAD].sum() / df[ColNames.GLOB_HOR_RAD].sum() * 100, 1)} %" tmp_unit = mapping_dictionary[ColNames.DBT][si_ip]["unit"] average_yearly_tmp = ( - f"Average yearly temperature: {df['DBT'].mean().round(1)} " + tmp_unit + f"Average yearly temperature: {df[ColNames.DBT].mean().round(1)} " + tmp_unit ) hottest_yearly_tmp = ( - f"Hottest yearly temperature (99%): {df['DBT'].quantile(0.99).round(1)} " + f"Hottest yearly temperature (99%): {df[ColNames.DBT].quantile(0.99).round(1)} " + tmp_unit ) coldest_yearly_tmp = ( - f"Coldest yearly temperature (1%): {df['DBT'].quantile(0.01).round(1)} " + f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} " + tmp_unit ) From 0aeeb25c16897820c1b048bcdc4adab2b228fe8c Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Mon, 25 Aug 2025 16:25:05 +1000 Subject: [PATCH 038/163] fix: resolved the conflict issues --- Pipfile.lock | 46 ++++++++----------- .../tab-summary/climate-profiles-explained.md | 4 +- pages/summary.py | 9 +++- 3 files changed, 30 insertions(+), 29 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index ef31819c..15c208f9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -751,14 +751,6 @@ "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" - }, "distlib": { "hashes": [ "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", @@ -925,25 +917,25 @@ }, "ruff": { "hashes": [ - "sha256:059e863ea3a9ade41407ad71c1de2badfbe01539117f38f763ba42a1206f7559", - "sha256:141ce3d88803c625257b8a6debf4a0473eb6eed9643a6189b68838b43e78165a", - "sha256:189ab65149d11ea69a2d775343adf5f49bb2426fc4780f65ee33b423ad2e47f9", - "sha256:1bef6161e297c68908b7218fa6e0e93e99a286e5ed9653d4be71e687dff101cf", - "sha256:1f68433c4fbc63efbfa3ba5db31727db229fa4e61000f452c540474b03de52a9", - "sha256:2c6f4064c69d2542029b2a61d39920c85240c39837599d7f2e32e80d36401d6e", - "sha256:37b4a64f4062a50c75019c61c7017ff598cb444984b638511f48539d3a1c98db", - "sha256:4f1345fbf8fb0531cd722285b5f15af49b2932742fc96b633e883da8d841896b", - "sha256:7837eca8787f076f67aba2ca559cefd9c5cbc3a9852fd66186f4201b87c1563e", - "sha256:7d1a4e0bdfafcd2e3e235ecf50bf0176f74dd37902f241588ae1f6c827a36c56", - "sha256:822d9677b560f1fdeab69b89d1f444bf5459da4aa04e06e766cf0121771ab844", - "sha256:8b593cb0fb55cc8692dac7b06deb29afda78c721c7ccfed22db941201b7b8f7b", - "sha256:9de785e95dc2f09846c5e6e1d3a3d32ecd0b283a979898ad427a9be7be22b266", - "sha256:ae479e1a18b439c59138f066ae79cc0f3ee250712a873d00dbafadaad9481e5b", - "sha256:cc138cc06ed9d4bfa9d667a65af7172b47840e1a98b02ce7011c391e54635ffc", - "sha256:d59e58586829f8e4a9920788f6efba97a13d1fa320b047814e8afede381c6839", - "sha256:e67d96827854f50b9e3e8327b031647e7bcc090dbe7bb11101a81a3a2cbf1cc9", - "sha256:ebb7333a45d56efc7c110a46a69a1b32365d5c5161e7244aaf3aa20ce62399c1", - "sha256:f3fc21178cd44c98142ae7590f42ddcb587b8e09a3b849cbc84edb62ee95de60" + "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", + "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", + "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", + "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", + "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", + "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", + "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", + "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", + "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", + "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", + "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", + "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", + "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", + "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", + "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", + "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", + "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", + "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", + "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e" ], "index": "pypi", "markers": "python_version >= '3.7'", diff --git a/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md b/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md index dc45d8e4..51756f30 100644 --- a/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md +++ b/docs/documentation/tabs-explained/tab-summary/climate-profiles-explained.md @@ -2,7 +2,9 @@ The Climate Profiles graph gives the user the opportunity to observe at a glance the distribution of the data in the EPW file for four key variables and their variation between day and night. -The Climate Profiles graph are [Violin Plots](https://en.wikipedia.org/wiki/Violin\_plot). They show the [probability density](https://en.wikipedia.org/wiki/Probability\_density\_function) of the data at different values, usually smoothed by a [kernel density estimator](https://en.wikipedia.org/wiki/Kernel\_density\_estimator). Wider sections of the violin plot represent a higher probability that members of the population will take on the given value; the skinnier sections represent a lower probability. +The Climate Profiles graph are [Violin Plots](https://en.wikipedia.org/wiki/Violin\_plot). +They show the [probability density](https://en.wikipedia.org/wiki/Probability\_density\_function) of the data at different values, usually smoothed by a [kernel density estimator](https://en.wikipedia.org/wiki/Kernel\_density\_estimator). +Wider sections of the violin plot represent a higher probability that members of the population will take on the given value; the skinnier sections represent a lower probability. On mouse hover, they display various statistical properties of the data: diff --git a/pages/summary.py b/pages/summary.py index 2230d3a8..199191f1 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -263,7 +263,14 @@ def update_location_info(ts, df, meta, si_ip): total_solar_rad_unit = "k" + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip]["unit"] total_solar_rad = f"Annual cumulative horizontal solar radiation: {total_solar_rad_value} {total_solar_rad_unit}" - total_diffuse_rad = f"Percentage of diffuse horizontal solar radiation: {round(df[ColNames.DIF_HOR_RAD].sum() / df[ColNames.GLOB_HOR_RAD].sum() * 100, 1)} %" + 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 + total_diffuse_rad = ( + f"Percentage of diffuse horizontal solar radiation: {diffuse_percentage} %" + ) tmp_unit = mapping_dictionary[ColNames.DBT][si_ip]["unit"] average_yearly_tmp = ( f"Average yearly temperature: {df[ColNames.DBT].mean().round(1)} " + tmp_unit From 9aee692a27314adb0a9015ae18c2114ff3194821 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Mon, 25 Aug 2025 17:17:20 +1000 Subject: [PATCH 039/163] fix: resolved the conflict issues for three md documentations --- .../tabs-explained/sun-and-cloud/README.md | 28 ++++++------------- .../customizable-daily-and-hourly-maps.md | 2 +- .../tabs-explained/tab-summary/README.md | 2 +- 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/docs/documentation/tabs-explained/sun-and-cloud/README.md b/docs/documentation/tabs-explained/sun-and-cloud/README.md index c54f5571..96aab5c8 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/README.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/README.md @@ -1,28 +1,18 @@ -# Sun and Clouds +# Sun and Cloud -The **Sun and Clouds** tab presents an overview of various climatic factors that relate to sun, solar position, intensity, and cloud cover, in particular: +### Sun and Cloud -* [Apparent sunpath for the location (spherical and cartesian projection)](broken-reference/) +The Sun and Clouds tab presents an overview of various climatic factors that relate to sun, solar position, intensity nad cloud cover, in particular: + +* [Apparent sunpath for the location \(spherical and cartesian projection\)](apparent-sunpath-for-the-location/) * [Global and Diffuse Horizontal Solar Radiation](global-and-diffuse-horizontal-solar-radiation/) * [Cloud coverage](cloud-coverage.md) * [Customizable daily and hourly maps](customizable-daily-and-hourly-maps.md) -### Apparent sun path for the location - -**Clima** allows the user to visualize the sun path for the chosen location in spherical and cartesian projection - -![Example: spherical sun path for Berlin, DEU](../../../.gitbook/assets/cbeclima\_berlin\_deu\_spherical\_sun\_path\_sun\_tab.svg) - -![Example: cartesian sun path for Berlin, DEU](../../../.gitbook/assets/cbeclima\_berlin\_deu\_cartesian\_sun\_path\_sun\_tab.svg) - -Clima optionally allows a variety of variables to be overlayed on either sun path type. - -This allows the user to identify climatic patterns in relation to the apparent solar position. Data are plotted on the analemma. - -![Spherical and carthesian sun paths for Berlin, DEU with various data overlays](../../../.gitbook/assets/sunpath+variables.png) +### Apparent sunpath for the location -### Video Tutorial +**Clima** allows the user to visualize the sunpath for the chosen location in spherical and cartesian projection -Learn more about the Sun and Clouds tab by watching the following video. +![Example: spherical sun path for Berlin, DEU ](../../.gitbook/assets/cbeclima_berlin_deu_spherical_sun_path_sun_tab.svg) -{% embed url="https://youtu.be/VJ_wOHadVdw?si=mB2xNH57MWW_4CRR&t=447" %} \ No newline at end of file +![Example: cartesiansun path for Berlin, DEU ](../../.gitbook/assets/cbeclima_berlin_deu_cartesian_sun_path_sun_tab.svg) \ No newline at end of file diff --git a/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md b/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md index c1dfc256..e226ac93 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/customizable-daily-and-hourly-maps.md @@ -10,4 +10,4 @@ The chart above shows the [scatter plot](https://en.wikipedia.org/wiki/Scatter\_ [Heat maps](https://en.wikipedia.org/wiki/Heat\_map) allow the intensity of values to be perceived through color palettes. These graphs are very helpful in seeing how magnitudes vary throughout the year. -

Examples of daily graphs with different variables (from top left to bottom right): global horizontal radiation, global horizontal illuminance, zenith luminance, opaque sky cover

\ No newline at end of file +

Examples of daily graphs with different variables (from top left to bottom right): global horizontal radiation, global horizontal illuminance, zenith luminance, opaque sky cover

diff --git a/docs/documentation/tabs-explained/tab-summary/README.md b/docs/documentation/tabs-explained/tab-summary/README.md index 4d474c10..8024c92e 100644 --- a/docs/documentation/tabs-explained/tab-summary/README.md +++ b/docs/documentation/tabs-explained/tab-summary/README.md @@ -13,4 +13,4 @@ The bottom section of the page comprises the heating and cooling degree day char Learn more about the Climate Summary tab by watching the following video. -{% embed url="https://youtu.be/VJ_wOHadVdw?si=H-93XRhh5Neuby_b&t=220" %} \ No newline at end of file +{% embed url="https://youtu.be/VJ_wOHadVdw?si=H-93XRhh5Neuby_b&t=220" %} From 5cc9259edf128e0d39a011db67bd72e4420996f2 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Mon, 25 Aug 2025 17:27:00 +1000 Subject: [PATCH 040/163] fix: resolved the conflict issues with global-diffuse-and-normal-solar-radiation-explained.md and the README.md which in sun-and-cloud folder --- .../tabs-explained/sun-and-cloud/README.md | 28 +++++++++++++------ ...se-and-normal-solar-radiation-explained.md | 2 +- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/documentation/tabs-explained/sun-and-cloud/README.md b/docs/documentation/tabs-explained/sun-and-cloud/README.md index 96aab5c8..29cbb1da 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/README.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/README.md @@ -1,18 +1,28 @@ -# Sun and Cloud +# Sun and Clouds -### Sun and Cloud +The **Sun and Clouds** tab presents an overview of various climatic factors that relate to sun, solar position, intensity, and cloud cover, in particular: -The Sun and Clouds tab presents an overview of various climatic factors that relate to sun, solar position, intensity nad cloud cover, in particular: - -* [Apparent sunpath for the location \(spherical and cartesian projection\)](apparent-sunpath-for-the-location/) +* [Apparent sunpath for the location (spherical and cartesian projection)](broken-reference/) * [Global and Diffuse Horizontal Solar Radiation](global-and-diffuse-horizontal-solar-radiation/) * [Cloud coverage](cloud-coverage.md) * [Customizable daily and hourly maps](customizable-daily-and-hourly-maps.md) -### Apparent sunpath for the location +### Apparent sun path for the location + +**Clima** allows the user to visualize the sun path for the chosen location in spherical and cartesian projection + +![Example: spherical sun path for Berlin, DEU](../../../.gitbook/assets/cbeclima\_berlin\_deu\_spherical\_sun\_path\_sun\_tab.svg) + +![Example: cartesian sun path for Berlin, DEU](../../../.gitbook/assets/cbeclima\_berlin\_deu\_cartesian\_sun\_path\_sun\_tab.svg) + +Clima optionally allows a variety of variables to be overlayed on either sun path type. + +This allows the user to identify climatic patterns in relation to the apparent solar position. Data are plotted on the analemma. + +![Spherical and carthesian sun paths for Berlin, DEU with various data overlays](../../../.gitbook/assets/sunpath+variables.png) -**Clima** allows the user to visualize the sunpath for the chosen location in spherical and cartesian projection +### Video Tutorial -![Example: spherical sun path for Berlin, DEU ](../../.gitbook/assets/cbeclima_berlin_deu_spherical_sun_path_sun_tab.svg) +Learn more about the Sun and Clouds tab by watching the following video. -![Example: cartesiansun path for Berlin, DEU ](../../.gitbook/assets/cbeclima_berlin_deu_cartesian_sun_path_sun_tab.svg) \ No newline at end of file +{% embed url="https://youtu.be/VJ_wOHadVdw?si=mB2xNH57MWW_4CRR&t=447" %} diff --git a/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/global-diffuse-and-normal-solar-radiation-explained.md b/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/global-diffuse-and-normal-solar-radiation-explained.md index f93f94de..fd87f855 100644 --- a/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/global-diffuse-and-normal-solar-radiation-explained.md +++ b/docs/documentation/tabs-explained/sun-and-cloud/global-and-diffuse-horizontal-solar-radiation/global-diffuse-and-normal-solar-radiation-explained.md @@ -13,4 +13,4 @@ $$ ![Conceptual representation of Global Horizontal, Diffuse Horizontal and Direct Normal Solar Radiation](../../../../.gitbook/assets/picture3.png) -![Measurement of Direct Irradiation on a horizontal and a normal plane](../../../../.gitbook/assets/picture4.png) +![Measurement of Direct Irradiation on a horizontal and a normal plane](../../../../.gitbook/assets/picture4.png) \ No newline at end of file From 54ce1d88c1d02910bb5a586adfd788d8e9cc5e6a Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Mon, 25 Aug 2025 18:21:11 +1000 Subject: [PATCH 041/163] fix: formatted the code style via using Black in global_column_names.py --- pages/lib/global_column_names.py | 39 +++++++++++++------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index c8c627d0..972ad76e 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -13,8 +13,8 @@ class ColNames(str, Enum): DBT = "DBT" # Dry Bulb Temperature DPT = "DPT" # Dew Point Temperature RH = "RH" # Relative Humidity - HI_RH = "hiRH" # High Relative Humidity - LO_RH = "loRH" # Low Relative Humidity + HI_RH = "hiRH" # High Relative Humidity + LO_RH = "loRH" # Low Relative Humidity HR = "hr" P_ATM = "p_atm" # Atmospheric Pressure @@ -34,8 +34,8 @@ class ColNames(str, Enum): ZLUMI = "Zlumi" # Luminance WIND_DIR = "wind_dir" # Wind Direction WIND_SPEED = "wind_speed" # Wind Speed - WIND_SPEED_UTCI = "wind_speed_utci" # Wind Speed Utci - WIND_SPEED_UTCI_0 = "wind_speed_utci_0" # Wind Speed Utci 0 + WIND_SPEED_UTCI = "wind_speed_utci" # Wind Speed Utci + WIND_SPEED_UTCI_0 = "wind_speed_utci_0" # Wind Speed Utci 0 TOT_SKY_COVER = "tot_sky_cover" # Total Sky Cover OSKYCOVER = "Oskycover" # Opaque Sky Cover VIS = "Vis" # Visibility @@ -46,21 +46,21 @@ class ColNames(str, Enum): AsolOptD = "AsolOptD" # Aerosol Optical Depth SnowD = "SnowD" # Snow Depth DaySSnow = "DaySSnow" # Daily Snow - ELEVATION = "elevation" # Elevation - APPARENT_ELEVATION = "apparent_elevation" # Apparent Elevation + ELEVATION = "elevation" # Elevation + APPARENT_ELEVATION = "apparent_elevation" # Apparent Elevation AZIMUTH = "azimuth" # Azimuth MRT = "MRT" DELTA_MRT = "delta_mrt" - UTCI_SUN_WIND = "utci_Sun_Wind" # Utci Sun Wind - UTCI_SUN_NO_WIND = "utci_Sun_noWind" # Utci Sun no Wind - UTCI_NO_SUN_WIND = "utci_noSun_Wind" # Utci no Sun Wind - UTCI_NO_SUN_NO_WIND = "utci_noSun_noWind" # Utci no Sun no Wind - ADAPTIVE_COMFORT = "adaptive_comfort" # Adaptive comfort - ADAPTIVE_CMF_80_LOW = "adaptive_cmf_80_low" # Adaptive comfort 80 low - ADAPTIVE_CMF_80_UP = "adaptive_cmf_80_up" # Adaptive comfort 80 up - ADAPTIVE_CMF_90_LOW = "adaptive_cmf_90_low" # Adaptive comfort 90 low - ADAPTIVE_CMF_90_UP = "adaptive_cmf_90_up" # Adaptive comfort 90 up - ADAPTIVE_CMF_RMT = "adaptive_cmf_rmt" # Adaptive comfort rmt + UTCI_SUN_WIND = "utci_Sun_Wind" # Utci Sun Wind + UTCI_SUN_NO_WIND = "utci_Sun_noWind" # Utci Sun no Wind + UTCI_NO_SUN_WIND = "utci_noSun_Wind" # Utci no Sun Wind + UTCI_NO_SUN_NO_WIND = "utci_noSun_noWind" # Utci no Sun no Wind + ADAPTIVE_COMFORT = "adaptive_comfort" # Adaptive comfort + ADAPTIVE_CMF_80_LOW = "adaptive_cmf_80_low" # Adaptive comfort 80 low + ADAPTIVE_CMF_80_UP = "adaptive_cmf_80_up" # Adaptive comfort 80 up + ADAPTIVE_CMF_90_LOW = "adaptive_cmf_90_low" # Adaptive comfort 90 low + ADAPTIVE_CMF_90_UP = "adaptive_cmf_90_up" # Adaptive comfort 90 up + ADAPTIVE_CMF_RMT = "adaptive_cmf_rmt" # Adaptive comfort rmt NV_ALLOWED = "nv_allowed" # ==================== Calculation column ==================== @@ -68,10 +68,3 @@ class ColNames(str, Enum): MONTH_NAMES = "month_names" # Month names UTC_TIME = "UTC_time" # UTC Time DOY = "DOY" # Day of Year - - - - - - - From a8c6d1d8affb7ef7ba17e40ced8d4c46df089929 Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Tue, 26 Aug 2025 18:38:45 +1000 Subject: [PATCH 042/163] Fix: updated the ElementIds in file wind --- pages/wind.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/wind.py b/pages/wind.py index 8517f510..92cc1284 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -378,7 +378,7 @@ def update_annual_wind_rose(_, df, meta, si_ip): # wind speed @callback( - Output(ColNames.WIND_SPEED, "children"), + Output(ElementIds.WIND_SPEED, "children"), # General [ Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), From cf63851a356a9e284fb7c1a98945adf3db49b645 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Tue, 26 Aug 2025 18:51:20 +1000 Subject: [PATCH 043/163] fix: replaced the string that using Class ColNames in charts_sun.py and charts_summary.py --- pages/lib/charts_summary.py | 12 ++++++------ pages/lib/charts_sun.py | 16 ++++++++-------- pages/lib/global_column_names.py | 20 ++++++++++++++------ 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/pages/lib/charts_summary.py b/pages/lib/charts_summary.py index ede7b264..6973564d 100644 --- a/pages/lib/charts_summary.py +++ b/pages/lib/charts_summary.py @@ -1,14 +1,14 @@ import pandas as pd import plotly.express as px - +from pages.lib.global_column_names import ColNames def world_map(meta): """Return the world map showing the current location.""" - latitude = float(meta["lat"]) - longitude = float(meta["lon"]) - city = meta["city"] - country = meta["country"] - time_zone = float(meta["time_zone"]) + latitude = float(meta[ColNames.LAT]) + longitude = float(meta[ColNames.LON]) + city = meta[ColNames.CITY] + country = meta[ColNames.COUNTRY] + time_zone = float(meta[ColNames.TIME_ZONE]) lat_long_df = pd.DataFrame( data={ "Lat": [latitude], diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 805fd717..a107475c 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -110,9 +110,9 @@ def monthly_solar(epw_df, si_ip): def polar_graph(df, meta, global_local, var, si_ip): """Return the figure for the custom sun path.""" - latitude = float(meta["lat"]) - longitude = float(meta["lon"]) - time_zone = float(meta["time_zone"]) + latitude = float(meta[ColNames.LAT]) + longitude = float(meta[ColNames.LON]) + time_zone = float(meta[ColNames.TIME_ZONE]) solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :] if var != "None": @@ -166,7 +166,7 @@ def polar_graph(df, meta, global_local, var, si_ip): if var == "None": fig.add_trace( go.Scatterpolar( - r=90 * np.cos(np.radians(90 - solpos["apparent_zenith"])), + r=90 * np.cos(np.radians(90 - solpos[ColNames.APPARENT_ZENITH])), theta=solpos[ColNames.AZIMUTH], mode="markers", marker_color="orange", @@ -196,7 +196,7 @@ def polar_graph(df, meta, global_local, var, si_ip): else: fig.add_trace( go.Scatterpolar( - r=90 * np.cos(np.radians(90 - solpos["apparent_zenith"])), + r=90 * np.cos(np.radians(90 - solpos[ColNames.APPARENT_ZENITH])), theta=solpos[ColNames.AZIMUTH], mode="markers", marker=dict( @@ -313,9 +313,9 @@ def polar_graph(df, meta, global_local, var, si_ip): def custom_cartesian_solar(df, meta, global_local, var, si_ip): """Return a graph of a latitude and longitude solar diagram.""" - latitude = float(meta["lat"]) - longitude = float(meta["lon"]) - time_zone = float(meta["time_zone"]) + latitude = float(meta[ColNames.LAT]) + longitude = float(meta[ColNames.LON]) + time_zone = float(meta[ColNames.TIME_ZONE]) tz = "UTC" if var != "None": diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index 972ad76e..a4471baf 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -9,6 +9,13 @@ class ColNames(str, Enum): HOUR = "hour" # hour MINUTE = "minute" # minute + # ==================== Location related column ==================== + LAT = "lat" # Latitude + LON = "lon" # Longitude + CITY = "city" # City + COUNTRY = "country" # Country + TIME_ZONE = "time_zone" # Time Zone + # ==================== Meteorological data column ==================== DBT = "DBT" # Dry Bulb Temperature DPT = "DPT" # Dew Point Temperature @@ -40,14 +47,15 @@ class ColNames(str, Enum): OSKYCOVER = "Oskycover" # Opaque Sky Cover VIS = "Vis" # Visibility CHEIGHT = "Cheight" # Cloud Height - PWobs = "PWobs" # Precipitation Observation - PWcodes = "PWcodes" # Precipitation Codes - Pwater = "Pwater" # Precipitation Water - AsolOptD = "AsolOptD" # Aerosol Optical Depth - SnowD = "SnowD" # Snow Depth - DaySSnow = "DaySSnow" # Daily Snow +# PWobs = "PWobs" # Precipitation Observation +# PWcodes = "PWcodes" # Precipitation Codes +# Pwater = "Pwater" # Precipitation Water +# AsolOptD = "AsolOptD" # Aerosol Optical Depth +# SnowD = "SnowD" # Snow Depth +# DaySSnow = "DaySSnow" # Daily Snow ELEVATION = "elevation" # Elevation APPARENT_ELEVATION = "apparent_elevation" # Apparent Elevation + APPARENT_ZENITH = "apparent_zenith" # Apparent Zenith AZIMUTH = "azimuth" # Azimuth MRT = "MRT" DELTA_MRT = "delta_mrt" From 373b44cedc435b7b3059cafe649c2a8f6de2f65c Mon Sep 17 00:00:00 2001 From: Ziqi Liu Date: Tue, 26 Aug 2025 19:15:27 +1000 Subject: [PATCH 044/163] Fix:Update the elements of global_column_names.py and charts_sun.py. --- pages/lib/charts_sun.py | 40 ++++++++++++++++++++------------ pages/lib/global_column_names.py | 28 +++++++++++++--------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index a107475c..17028014 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -20,10 +20,14 @@ def monthly_solar(epw_df, si_ip): g_h_rad_month_ave = ( - epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.GLOB_HOR_RAD].median().reset_index() + epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.GLOB_HOR_RAD] + .median() + .reset_index() ) dif_h_rad_month_ave = ( - epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.DIF_HOR_RAD].median().reset_index() + epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.DIF_HOR_RAD] + .median() + .reset_index() ) fig = make_subplots( rows=1, @@ -38,7 +42,9 @@ def monthly_solar(epw_df, si_ip): fig.add_trace( go.Scatter( - x=g_h_rad_month_ave.loc[g_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR], + x=g_h_rad_month_ave.loc[ + g_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR + ], y=g_h_rad_month_ave.loc[ g_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.GLOB_HOR_RAD ], @@ -48,12 +54,14 @@ def monthly_solar(epw_df, si_ip): line_width=2, name="Global", showlegend=is_first, - customdata=epw_df.loc[epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES], + customdata=epw_df.loc[ + epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES + ], hovertemplate=( "" + "Global Horizontal Solar Radiation" + ": %{y:.2f} " - + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip]["unit"] + + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip][ColNames.UNIT] + "
" + "Month: %{customdata}
" + "Hour: %{x}:00
" @@ -78,12 +86,14 @@ def monthly_solar(epw_df, si_ip): line_width=2, name="Diffuse", showlegend=is_first, - customdata=epw_df.loc[epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES], + customdata=epw_df.loc[ + epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES + ], hovertemplate=( "" + "Diffuse Horizontal Solar Radiation" + ": %{y:.2f} " - + mapping_dictionary[ColNames.DIF_HOR_RAD][si_ip]["unit"] + + mapping_dictionary[ColNames.DIF_HOR_RAD][si_ip][ColNames.UNIT] + "
" + "Month: %{customdata}
" + "Hour: %{x}:00
" @@ -116,10 +126,10 @@ def polar_graph(df, meta, global_local, var, si_ip): solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :] if var != "None": - var_unit = mapping_dictionary[var][si_ip]["unit"] - var_range = mapping_dictionary[var][si_ip]["range"] - var_name = mapping_dictionary[var]["name"] - var_color = mapping_dictionary[var]["color"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + var_name = mapping_dictionary[var][ColNames.NAME] + var_color = mapping_dictionary[var][ColNames.COLOR] if global_local == "global": # Set Global values for Max and minimum range_z = var_range @@ -319,10 +329,10 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): tz = "UTC" if var != "None": - var_unit = mapping_dictionary[var][si_ip]["unit"] - var_range = mapping_dictionary[var][si_ip]["range"] - var_name = mapping_dictionary[var]["name"] - var_color = mapping_dictionary[var]["color"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + var_name = mapping_dictionary[var][ColNames.NAME] + var_color = mapping_dictionary[var][ColNames.COLOR] if global_local == "global": # Set Global values for Max and minimum range_z = var_range diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index a4471baf..28317967 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -10,11 +10,11 @@ class ColNames(str, Enum): MINUTE = "minute" # minute # ==================== Location related column ==================== - LAT = "lat" # Latitude - LON = "lon" # Longitude - CITY = "city" # City - COUNTRY = "country" # Country - TIME_ZONE = "time_zone" # Time Zone + LAT = "lat" # Latitude + LON = "lon" # Longitude + CITY = "city" # City + COUNTRY = "country" # Country + TIME_ZONE = "time_zone" # Time Zone # ==================== Meteorological data column ==================== DBT = "DBT" # Dry Bulb Temperature @@ -47,12 +47,12 @@ class ColNames(str, Enum): OSKYCOVER = "Oskycover" # Opaque Sky Cover VIS = "Vis" # Visibility CHEIGHT = "Cheight" # Cloud Height -# PWobs = "PWobs" # Precipitation Observation -# PWcodes = "PWcodes" # Precipitation Codes -# Pwater = "Pwater" # Precipitation Water -# AsolOptD = "AsolOptD" # Aerosol Optical Depth -# SnowD = "SnowD" # Snow Depth -# DaySSnow = "DaySSnow" # Daily Snow + # PWobs = "PWobs" # Precipitation Observation + # PWcodes = "PWcodes" # Precipitation Codes + # Pwater = "Pwater" # Precipitation Water + # AsolOptD = "AsolOptD" # Aerosol Optical Depth + # SnowD = "SnowD" # Snow Depth + # DaySSnow = "DaySSnow" # Daily Snow ELEVATION = "elevation" # Elevation APPARENT_ELEVATION = "apparent_elevation" # Apparent Elevation APPARENT_ZENITH = "apparent_zenith" # Apparent Zenith @@ -76,3 +76,9 @@ class ColNames(str, Enum): MONTH_NAMES = "month_names" # Month names UTC_TIME = "UTC_time" # UTC Time DOY = "DOY" # Day of Year + + COLOR = "color" + NAME = "name" + RANGE = "range" + UNIT = "unit" + TWENTY_FOUR_HOUR = "24h" From f03a6ede15ee9890bbb9cf404c0e1a5cecbcd941 Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Tue, 26 Aug 2025 19:31:18 +1000 Subject: [PATCH 045/163] Fix: Update the column name in file charts_data_explorer.py --- pages/lib/charts_data_explorer.py | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index d1534057..c747763f 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -31,12 +31,12 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si if df.dropna(subset=[var]).shape[0] == 0: return None - var_unit = mapping_dictionary[var][si_ip]["unit"] - var_range = mapping_dictionary[var][si_ip]["range"] - var_name = mapping_dictionary[var]["name"] - var_color = mapping_dictionary[var]["color"] - filter_name = mapping_dictionary[filter_var]["name"] - filter_unit = mapping_dictionary[filter_var][si_ip]["unit"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + var_name = mapping_dictionary[var][ColNames.NAME] + var_color = mapping_dictionary[var][ColNames.COLOR] + filter_name = mapping_dictionary[filter_var][ColNames.NAME] + filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT] if global_local == "global": # Set Global values for Max and minimum @@ -112,12 +112,12 @@ def three_var_graph( min_val = data_filter_info3[2] max_val = data_filter_info3[3] - var_unit_x = mapping_dictionary[var_x][si_ip]["unit"] - var_unit_y = mapping_dictionary[var_y][si_ip]["unit"] + var_unit_x = mapping_dictionary[var_x][si_ip][ColNames.UNIT] + var_unit_y = mapping_dictionary[var_y][si_ip][ColNames.UNIT] var = color_by - var_range = mapping_dictionary[var][si_ip]["range"] - var_color = mapping_dictionary[var]["color"] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + var_color = mapping_dictionary[var][ColNames.COLOR] if global_local != "global": # Set maximum and minimum according to data @@ -137,11 +137,11 @@ def three_var_graph( return None title = ( - mapping_dictionary[var_x]["name"] + mapping_dictionary[var_x][ColNames.NAME] + " vs " - + mapping_dictionary[var_y]["name"] + + mapping_dictionary[var_y][ColNames.NAME] + " colored by " - + mapping_dictionary[color_by]["name"] + + mapping_dictionary[color_by][ColNames.NAME] ) fig = px.scatter( @@ -168,13 +168,13 @@ def three_var_graph( def two_var_graph(df, var_x, var_y, si_ip): title = ( "Simultaneous frequency of " - + mapping_dictionary[var_x]["name"] + + mapping_dictionary[var_x][ColNames.NAME] + " and " - + mapping_dictionary[var_y]["name"] + + mapping_dictionary[var_y][ColNames.NAME] ) - var_unit_x = mapping_dictionary[var_x][si_ip]["unit"] - var_unit_y = mapping_dictionary[var_y][si_ip]["unit"] + var_unit_x = mapping_dictionary[var_x][si_ip][ColNames.UNIT] + var_unit_y = mapping_dictionary[var_y][si_ip][ColNames.UNIT] fig = px.density_heatmap( df, From 722caca33513731de14e1cf97ec5533eb59dfa12 Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Tue, 26 Aug 2025 19:41:53 +1000 Subject: [PATCH 046/163] Fix: Update the column name in file extract_df.py anad global_column_names.py --- pages/lib/extract_df.py | 10 +++++----- pages/lib/global_column_names.py | 6 ++++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index ce2df0fa..55b76e76 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -329,11 +329,11 @@ def create_df(lst, file_name): limit_inputs=False, ) epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_RMT] = rmt - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_COMFORT] = r["tmp_cmf"] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_LOW] = r["tmp_cmf_80_low"] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_UP] = r["tmp_cmf_80_up"] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_LOW] = r["tmp_cmf_90_low"] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_UP] = r["tmp_cmf_90_up"] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_COMFORT] = r[ColNames.TMP_CMF] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_LOW] = r[ColNames.TMP_CMF_80_LOW] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_UP] = r[ColNames.TMP_CMF_80_UP] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_LOW] = r[ColNames.TMP_CMF_90_LOW] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_UP] = r[ColNames.TMP_CMF_90_UP] return epw_df, location_info diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index 28317967..ece9a146 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -70,6 +70,12 @@ class ColNames(str, Enum): ADAPTIVE_CMF_90_UP = "adaptive_cmf_90_up" # Adaptive comfort 90 up ADAPTIVE_CMF_RMT = "adaptive_cmf_rmt" # Adaptive comfort rmt NV_ALLOWED = "nv_allowed" + TMP_CMF = "tmp_cmf" + TMP_CMF_80_LOW = "tmp_cmf_80_low" + TMP_CMF_80_UP = "tmp_cmf_80_up" + TMP_CMF_90_LOW = "tmp_cmf_90_low" + TMP_CMF_90_UP = "tmp_cmf_90_up" + # ==================== Calculation column ==================== FAKE_YEAR = "fake_year" # Fake Year From 73b2f8a4b28140f247d25e905bc3f286794e551b Mon Sep 17 00:00:00 2001 From: Ziqi Liu Date: Tue, 26 Aug 2025 19:59:19 +1000 Subject: [PATCH 047/163] Fix:Update the elements of template_graphs.py. --- pages/lib/template_graphs.py | 85 ++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index ae840591..b0f20f1f 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -17,9 +17,9 @@ def violin(df, var, global_local, si_ip): """Return day night violin based on the 'var' col""" mask_day = (df[ColNames.HOUR] >= 8) & (df[ColNames.HOUR] < 20) mask_night = (df[ColNames.HOUR] < 8) | (df[ColNames.HOUR] >= 20) - var_unit = mapping_dictionary[var][si_ip]["unit"] - var_range = mapping_dictionary[var][si_ip]["range"] - var_name = mapping_dictionary[var]["name"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + var_name = mapping_dictionary[var][ColNames.NAME] data_day = df.loc[mask_day, var] data_night = df.loc[mask_night, var] @@ -85,10 +85,10 @@ def violin(df, var, global_local, si_ip): @code_timer def yearly_profile(df, var, global_local, si_ip): """Return yearly profile figure based on the 'var' col.""" - var_unit = mapping_dictionary[var][si_ip]["unit"] - var_range = mapping_dictionary[var][si_ip]["range"] - var_name = mapping_dictionary[var]["name"] - var_color = mapping_dictionary[var]["color"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + var_name = mapping_dictionary[var][ColNames.NAME] + var_color = mapping_dictionary[var][ColNames.COLOR] if global_local == "global": # Set Global values for Max and minimum @@ -114,7 +114,11 @@ def yearly_profile(df, var, global_local, si_ip): marker_opacity=0.3, name=var_name + " Range", customdata=np.stack( - (dbt_day["mean"], df.iloc[::24, :][ColNames.MONTH_NAMES], df.iloc[::24, :][ColNames.DAY]), + ( + dbt_day["mean"], + df.iloc[::24, :][ColNames.MONTH_NAMES], + df.iloc[::24, :][ColNames.DAY], + ), axis=-1, ), hovertemplate=( @@ -136,7 +140,11 @@ def yearly_profile(df, var, global_local, si_ip): marker_color=var_single_color, marker_opacity=1, customdata=np.stack( - (dbt_day["mean"], df.iloc[::24, :][ColNames.MONTH_NAMES], df.iloc[::24, :][ColNames.DAY]), + ( + dbt_day["mean"], + df.iloc[::24, :][ColNames.MONTH_NAMES], + df.iloc[::24, :][ColNames.DAY], + ), axis=-1, ), hovertemplate=( @@ -238,10 +246,10 @@ def yearly_profile(df, var, global_local, si_ip): # @code_timer def daily_profile(df, var, global_local, si_ip): """Return the daily profile based on the 'var' col.""" - var_name = mapping_dictionary[var]["name"] - var_unit = mapping_dictionary[var][si_ip]["unit"] - var_range = mapping_dictionary[var][si_ip]["range"] - var_color = mapping_dictionary[var]["color"] + var_name = mapping_dictionary[var][ColNames.NAME] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + var_color = mapping_dictionary[var][ColNames.COLOR] if global_local == "global": # Set Global values for Max and minimum range_y = var_range @@ -252,7 +260,9 @@ def daily_profile(df, var, global_local, si_ip): range_y = [data_min, data_max] var_single_color = var_color[len(var_color) // 2] - var_month_ave = df.groupby([ColNames.MONTH, ColNames.HOUR])[var].median().reset_index() + var_month_ave = ( + df.groupby([ColNames.MONTH, ColNames.HOUR])[var].median().reset_index() + ) fig = make_subplots( rows=1, cols=12, @@ -286,7 +296,9 @@ def daily_profile(df, var, global_local, si_ip): fig.add_trace( go.Scatter( - x=var_month_ave.loc[var_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR], + x=var_month_ave.loc[ + var_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR + ], y=var_month_ave.loc[var_month_ave[ColNames.MONTH] == i + 1, var], mode="lines", line_color=var_single_color, @@ -331,9 +343,9 @@ def heatmap_with_filter( title, ): """General function that returns a heatmap.""" - var_unit = mapping_dictionary[var][si_ip]["unit"] - var_range = mapping_dictionary[var][si_ip]["range"] - var_color = mapping_dictionary[var]["color"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + var_color = mapping_dictionary[var][ColNames.COLOR] df = filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, var @@ -409,9 +421,9 @@ def heatmap_with_filter( def heatmap(df, var, global_local, si_ip): """General function that returns a heatmap.""" - var_unit = mapping_dictionary[var][si_ip]["unit"] - var_range = mapping_dictionary[var][si_ip]["range"] - var_color = mapping_dictionary[var]["color"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + var_color = mapping_dictionary[var][ColNames.COLOR] if global_local == "global": # Set Global values for Max and minimum @@ -478,16 +490,20 @@ def wind_rose(df, title, month, hour, labels, si_ip): start_hour = hour[0] end_hour = hour[1] if start_month <= end_month: - df = df.loc[(df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month)] + df = df.loc[ + (df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month) + ] else: - df = df.loc[(df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month)] + df = df.loc[ + (df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month) + ] if start_hour <= end_hour: 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)] - spd_colors = mapping_dictionary[ColNames.WIND_SPEED]["color"] - spd_unit = mapping_dictionary[ColNames.WIND_SPEED][si_ip]["unit"] + spd_colors = mapping_dictionary[ColNames.WIND_SPEED][ColNames.COLOR] + spd_unit = mapping_dictionary[ColNames.WIND_SPEED][si_ip][ColNames.UNIT] spd_bins = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] if si_ip == UnitSystem.IP: spd_bins = convert_bins(spd_bins) @@ -631,7 +647,10 @@ def thermal_stress_stacked_barchart( isNormalized = True if len(normalize) != 0 else False if isNormalized: new_df = ( - df.groupby(ColNames.MONTH)[var].value_counts(normalize=True).unstack(var).fillna(0) + df.groupby(ColNames.MONTH)[var] + .value_counts(normalize=True) + .unstack(var) + .fillna(0) ) new_df = new_df.set_axis(categories, axis=1) new_df.reset_index(inplace=True) @@ -714,12 +733,12 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): end_hour = time_filter_info[2][1] filter_var = str(data_filter_info[1]) - filter_name = mapping_dictionary[filter_var]["name"] - filter_unit = mapping_dictionary[filter_var][si_ip]["unit"] + filter_name = mapping_dictionary[filter_var][ColNames.NAME] + filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT] - var_unit = mapping_dictionary[var][si_ip]["unit"] - var_name = mapping_dictionary[var]["name"] - var_color = mapping_dictionary[var]["color"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_name = mapping_dictionary[var][ColNames.NAME] + var_color = mapping_dictionary[var][ColNames.COLOR] color_below = var_color[0] color_above = var_color[-1] @@ -833,7 +852,9 @@ def filter_df_by_month_and_hour( mask = (df[ColNames.MONTH] < start_month) | (df[ColNames.MONTH] > end_month) df.loc[mask, var] = None else: - mask = (df[ColNames.MONTH] >= end_month) & (df[ColNames.MONTH] <= start_month) + mask = (df[ColNames.MONTH] >= end_month) & ( + df[ColNames.MONTH] <= start_month + ) df.loc[mask, var] = None if start_hour <= end_hour: From b8568e3a8b774e61debc0bdd052d7918bee2505f Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Tue, 26 Aug 2025 20:01:32 +1000 Subject: [PATCH 048/163] Fix: Update the column name in file global_scheme.py and psy-chart.py --- pages/lib/global_scheme.py | 11 ++++++----- pages/psy-chart.py | 34 +++++++++++++++++----------------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index b71d008c..b0ff2e6e 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,4 +1,5 @@ import plotly.io as pio +from global_column_names import ColNames from config import UnitSystem @@ -900,20 +901,20 @@ ] sun_cloud_tab_dropdown_names = { - mapping_dictionary[key]["name"]: key for key in variables_sun_cloud_tab_dropdown + mapping_dictionary[key][ColNames.NAME]: key for key in variables_sun_cloud_tab_dropdown } -dropdown_names = {mapping_dictionary[key]["name"]: key for key in variables_dropdown} +dropdown_names = {mapping_dictionary[key][ColNames.NAME]: key for key in variables_dropdown} more_variables_dropdown = { - mapping_dictionary[key]["name"]: key for key in variables_more_variables_dropdown + mapping_dictionary[key][ColNames.NAME]: key for key in variables_more_variables_dropdown } sun_cloud_tab_explore_dropdown_names = { - mapping_dictionary[key]["name"]: key + mapping_dictionary[key][ColNames.NAME]: key for key in variables_sun_cloud_tab_explore_dropdown } outdoor_dropdown_names = { - mapping_dictionary[key]["name"]: key for key in variables_outdoor_dropdown + mapping_dictionary[key][ColNames.NAME]: key for key in variables_outdoor_dropdown } diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 7fbd802d..9f934723 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -306,16 +306,16 @@ def update_psych_chart( elif var == "Frequency": var_color = ["rgba(255,255,255,0)", "rgb(0,150,255)", "rgb(0,0,150)"] else: - var_unit = mapping_dictionary[var][si_ip]["unit"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_name = mapping_dictionary[var]["name"] + var_name = mapping_dictionary[var][ColNames.NAME] - var_color = mapping_dictionary[var]["color"] + var_color = mapping_dictionary[var][ColNames.COLOR] if global_local == "global": # Set Global values for Max and minimum - var_range_x = mapping_dictionary[ColNames.DBT][si_ip]["range"] - var_range_y = mapping_dictionary[ColNames.HR][si_ip]["range"] + var_range_x = mapping_dictionary[ColNames.DBT][si_ip][ColNames.RANGE] + var_range_y = mapping_dictionary[ColNames.HR][si_ip][ColNames.RANGE] else: # Set maximum and minimum according to data @@ -386,9 +386,9 @@ def update_psych_chart( showscale=False, opacity=0.2, ), - hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + hovertemplate=mapping_dictionary[ColNames.DBT][ColNames.NAME] + ": %{x:.2f}" - + mapping_dictionary[ColNames.DBT]["name"], + + mapping_dictionary[ColNames.DBT][ColNames.NAME], name="", ) ) @@ -452,21 +452,21 @@ def update_psych_chart( colorbar=var_colorbar, ), customdata=np.stack((df[ColNames.RH], df["h"], df[var], df["t_dp"]), axis=-1), - hovertemplate=mapping_dictionary[ColNames.DBT]["name"] + hovertemplate=mapping_dictionary[ColNames.DBT][ColNames.NAME] + ": %{x:.2f}" - + mapping_dictionary[ColNames.DBT][si_ip]["unit"] + + mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT] + "
" - + mapping_dictionary[ColNames.RH]["name"] + + mapping_dictionary[ColNames.RH][ColNames.NAME] + ": %{customdata[0]:.2f}" - + mapping_dictionary[ColNames.RH][si_ip]["unit"] + + mapping_dictionary[ColNames.RH][si_ip][ColNames.UNIT] + "
" - + mapping_dictionary["h"]["name"] + + mapping_dictionary["h"][ColNames.NAME] + ": %{customdata[1]:.2f}" - + mapping_dictionary["h"][si_ip]["unit"] + + mapping_dictionary["h"][si_ip][ColNames.UNIT] + "
" - + mapping_dictionary["t_dp"]["name"] + + mapping_dictionary["t_dp"][ColNames.NAME] + ": %{customdata[3]:.2f}" - + mapping_dictionary["t_dp"][si_ip]["unit"] + + mapping_dictionary["t_dp"][si_ip][ColNames.UNIT] + "
" + "
" + var_name @@ -476,8 +476,8 @@ def update_psych_chart( ) ) - xtitle_name = "Temperature" + " " + mapping_dictionary[ColNames.DBT][si_ip]["unit"] - ytitle_name = "Humidity Ratio" + " " + mapping_dictionary[ColNames.HR][si_ip]["unit"] + xtitle_name = "Temperature" + " " + mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT] + ytitle_name = "Humidity Ratio" + " " + mapping_dictionary[ColNames.HR][si_ip][ColNames.UNIT] fig.update_layout(template=template, margin=tight_margins) fig.update_xaxes( title_text=xtitle_name, From 6adf2fce1391c31001468f28485f55fb83a391b2 Mon Sep 17 00:00:00 2001 From: Ziqi Liu Date: Wed, 27 Aug 2025 13:45:54 +1000 Subject: [PATCH 049/163] Fix:Update the elements of global_scheme.py, natural_ventilation.py, psy-chart.py, select.py, summary.py. --- pages/lib/global_scheme.py | 2 +- pages/natural_ventilation.py | 25 +++++++++++++------------ pages/psy-chart.py | 2 +- pages/select.py | 3 ++- pages/summary.py | 4 ++-- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index b0ff2e6e..2f984aac 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,5 +1,5 @@ import plotly.io as pio -from global_column_names import ColNames +from pages.lib.global_column_names import ColNames from config import UnitSystem diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 091d8fa4..073f4250 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,4 +1,3 @@ - import math import dash @@ -357,17 +356,17 @@ def nv_heatmap( df, time_filter, month, hour, invert_month, invert_hour, var ) - var_unit = mapping_dictionary[var][si_ip]["unit"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - filter_unit = mapping_dictionary[filter_var][si_ip]["unit"] + filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT] - var_range = mapping_dictionary[var][si_ip]["range"] + var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_name = mapping_dictionary[var]["name"] + var_name = mapping_dictionary[var][ColNames.NAME] - filter_name = mapping_dictionary[filter_var]["name"] + filter_name = mapping_dictionary[filter_var][ColNames.NAME] - var_color = mapping_dictionary[var]["color"] + var_color = mapping_dictionary[var][ColNames.COLOR] if global_local == "global": range_z = var_range @@ -502,12 +501,12 @@ def nv_bar_chart( var = "DBT" filter_var = "DPT" - var_unit = mapping_dictionary[var][si_ip]["unit"] - filter_unit = mapping_dictionary[filter_var][si_ip]["unit"] + var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT] - var_name = mapping_dictionary[var]["name"] + var_name = mapping_dictionary[var][ColNames.NAME] - filter_name = mapping_dictionary[filter_var]["name"] + filter_name = mapping_dictionary[filter_var][ColNames.NAME] color_in = "dodgerblue" @@ -523,7 +522,9 @@ def nv_bar_chart( ) if dbt_data_filter and (min_dbt_val <= max_dbt_val): - df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), ColNames.NV_ALLOWED] = 0 + df.loc[ + (df[var] < min_dbt_val) | (df[var] > max_dbt_val), ColNames.NV_ALLOWED + ] = 0 if dpt_data_filter: df.loc[(df[filter_var] > max_dpt_val), ColNames.NV_ALLOWED] = 0 diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 9f934723..70c137c1 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -289,7 +289,7 @@ def update_psych_chart( mask = (df[data_filter_var] >= max_val) & (df[data_filter_var] <= min_val) df[mask] = None - if df.dropna(subset=["month"]).shape[0] == 0: + if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return ( dbc.Alert( "No data is available in this location under these conditions. Please " diff --git a/pages/select.py b/pages/select.py index 89fd67f0..b4b202cb 100644 --- a/pages/select.py +++ b/pages/select.py @@ -13,6 +13,7 @@ from pages.lib.extract_df import convert_data from pages.lib.extract_df import create_df, get_data, get_location_info +from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds from pages.lib.global_scheme import mapping_dictionary from config import PageUrls, PageInfo, UnitSystem @@ -350,7 +351,7 @@ def plot_location_epw_files(pathname): df_one_building, lat="lat", lon="lon", - hover_name=df_one_building["name"], + hover_name=df_one_building[ColNames.NAME], color_discrete_sequence=["#4895ef"], hover_data=[ "period", diff --git a/pages/summary.py b/pages/summary.py index 199191f1..691fa233 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -260,7 +260,7 @@ def update_location_info(ts, df, meta, si_ip): # 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]["unit"] + total_solar_rad_unit = "k" + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip][ColNames.UNIT] 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() @@ -271,7 +271,7 @@ def update_location_info(ts, df, meta, si_ip): total_diffuse_rad = ( f"Percentage of diffuse horizontal solar radiation: {diffuse_percentage} %" ) - tmp_unit = mapping_dictionary[ColNames.DBT][si_ip]["unit"] + 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 ) From dceb8567a219e9d84dd2c25cd2a7a87a6359ec7d Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Wed, 27 Aug 2025 13:50:32 +1000 Subject: [PATCH 050/163] Fix: Update the column name in file global_scheme.py --- pages/lib/global_scheme.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index b0ff2e6e..2f984aac 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,5 +1,5 @@ import plotly.io as pio -from global_column_names import ColNames +from pages.lib.global_column_names import ColNames from config import UnitSystem From a75124effdeb7718e0f93d39fcada58f565b0be6 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Wed, 27 Aug 2025 15:15:43 +1000 Subject: [PATCH 051/163] fix: replaced the string that using Class ColNames in utils.py and summary.py and extract_df.py --- pages/lib/extract_df.py | 30 +++++++++++++++--------------- pages/lib/global_column_names.py | 7 +++++++ pages/lib/utils.py | 12 ++++++------ pages/summary.py | 18 +++++++++--------- 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 55b76e76..451e1b8d 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -68,7 +68,7 @@ def get_location_info(lst, file_name): # from OneClimaBuilding files extract info about reference years try: - location_info["period"] = re.search(r'cord=[\'"]?([^\'" >]+);', lst[5]).group(1) + location_info[ColNames.PERIOD] = re.search(r'cord=[\'"]?([^\'" >]+);', lst[5]).group(1) except AttributeError: pass @@ -94,7 +94,7 @@ def create_df(lst, file_name): # from OneClimaBuilding files extract info about reference years try: - location_info["period"] = re.search(r'cord=[\'"]?([^\'" >]+);', lst[5]).group(1) + location_info[ColNames.PERIOD] = re.search(r'cord=[\'"]?([^\'" >]+);', lst[5]).group(1) except AttributeError: pass @@ -150,15 +150,15 @@ def create_df(lst, file_name): epw_df = pd.DataFrame(columns=col_names, data=lst) # from EnergyPlus files extract info about reference years - if not location_info["period"]: + if not location_info[ColNames.PERIOD]: years = epw_df[ColNames.YEAR].astype("int").unique() if len(years) == 1: year_rounded_up = int(math.ceil(years[0] / 10.0)) * 10 - location_info["period"] = f"{year_rounded_up - 10}-{year_rounded_up}" + location_info[ColNames.PERIOD] = f"{year_rounded_up - 10}-{year_rounded_up}" else: min_year = int(math.floor(min(years) / 10.0)) * 10 max_year = int(math.ceil(max(years) / 10.0)) * 10 - location_info["period"] = f"{min_year}-{max_year}" + location_info[ColNames.PERIOD] = f"{min_year}-{max_year}" # Add fake_year epw_df[ColNames.FAKE_YEAR] = ColNames.YEAR @@ -213,16 +213,16 @@ def create_df(lst, file_name): "2019-01-01 00:00:00", "2020-01-01", inclusive="left", freq="h", tz="UTC" ) epw_df[ColNames.UTC_TIME] = pd.to_datetime(times) - delta = timedelta(days=0, hours=location_info["time_zone"] - 1, minutes=0) + delta = timedelta(days=0, hours=location_info[ColNames.TIME_ZONE] - 1, minutes=0) times = times - delta - epw_df["times"] = times + epw_df[ColNames.TIMES] = times epw_df.set_index( - "times", drop=False, append=False, inplace=True, verify_integrity=False + ColNames.TIMES, drop=False, append=False, inplace=True, verify_integrity=False ) # Add in solar position df solar_position = solarposition.get_solarposition( - times, location_info["lat"], location_info["lon"] + times, location_info[ColNames.LAT], location_info[ColNames.LON] ) epw_df = pd.concat([epw_df, solar_position], axis=1) @@ -280,16 +280,16 @@ def create_df(lst, file_name): utci_bins = [-999, -40, -27, -13, 0, 9, 26, 32, 38, 46, 999] utci_labels = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] - epw_df["utci_noSun_Wind_categories"] = pd.cut( + epw_df[ColNames.UTCI_NOSUN_WIND_CATEGORIES] = pd.cut( x=epw_df[ColNames.UTCI_NO_SUN_WIND], bins=utci_bins, labels=utci_labels ) - epw_df["utci_noSun_noWind_categories"] = pd.cut( + epw_df[ColNames.UTCI_NOSUN_NOWIND_CATEGORIES] = pd.cut( x=epw_df[ColNames.UTCI_NO_SUN_NO_WIND], bins=utci_bins, labels=utci_labels ) - epw_df["utci_Sun_Wind_categories"] = pd.cut( + epw_df[ColNames.UTCI_SUN_WIND_CATEGORIES] = pd.cut( x=epw_df[ColNames.UTCI_SUN_WIND], bins=utci_bins, labels=utci_labels ) - epw_df["utci_Sun_noWind_categories"] = pd.cut( + epw_df[ColNames.UTCI_SUN_NOWIND_CATEGORIES] = pd.cut( x=epw_df[ColNames.UTCI_SUN_NO_WIND], bins=utci_bins, labels=utci_labels ) @@ -380,8 +380,8 @@ def convert_data(df, mapping_json): mapping_dict = json.loads(mapping_json) for key in json.loads(mapping_json): - if "conversion_function" in mapping_dict[key]: - conversion_function_name = mapping_dict[key]["conversion_function"] + if ColNames.CONVERSION_FUNCTION in mapping_dict[key]: + conversion_function_name = mapping_dict[key][ColNames.CONVERSION_FUNCTION] if conversion_function_name is not None: conversion_function = globals()[conversion_function_name] conversion_function(df, key) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index ece9a146..dcae7541 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -4,6 +4,7 @@ class ColNames(str, Enum): # ==================== Time related column ==================== YEAR = "year" # year + PERIOD = "period" # period MONTH = "month" # month DAY = "day" # day HOUR = "hour" # hour @@ -63,6 +64,10 @@ class ColNames(str, Enum): UTCI_SUN_NO_WIND = "utci_Sun_noWind" # Utci Sun no Wind UTCI_NO_SUN_WIND = "utci_noSun_Wind" # Utci no Sun Wind UTCI_NO_SUN_NO_WIND = "utci_noSun_noWind" # Utci no Sun no Wind + UTCI_SUN_WIND_CATEGORIES = "utci_Sun_Wind_categories" # Utci Sun Wind Categories + UTCI_SUN_NOWIND_CATEGORIES = "utci_Sun_noWind_categories" # Utci Sun no Wind Categories + UTCI_NOSUN_WIND_CATEGORIES = "utci_noSun_Wind_categories" # Utci no Sun Wind Categories + UTCI_NOSUN_NOWIND_CATEGORIES = "utci_noSun_noWind_categories" # Utci no Sun no Wind Categories ADAPTIVE_COMFORT = "adaptive_comfort" # Adaptive comfort ADAPTIVE_CMF_80_LOW = "adaptive_cmf_80_low" # Adaptive comfort 80 low ADAPTIVE_CMF_80_UP = "adaptive_cmf_80_up" # Adaptive comfort 80 up @@ -75,6 +80,7 @@ class ColNames(str, Enum): TMP_CMF_80_UP = "tmp_cmf_80_up" TMP_CMF_90_LOW = "tmp_cmf_90_low" TMP_CMF_90_UP = "tmp_cmf_90_up" + CONVERSION_FUNCTION = "conversion_function" # ==================== Calculation column ==================== @@ -88,3 +94,4 @@ class ColNames(str, Enum): RANGE = "range" UNIT = "unit" TWENTY_FOUR_HOUR = "24h" + TIMES = "times" diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 89038bae..b40e39a8 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -55,7 +55,7 @@ def generate_units_degree(si_ip): def generate_custom_inputs(var): if var in mapping_dictionary: - var_fullname = mapping_dictionary[var]["name"] + var_fullname = mapping_dictionary[var][ColNames.NAME] custom_inputs = "".join(word.capitalize() for word in var_fullname.split(" ")) return custom_inputs else: @@ -90,14 +90,14 @@ def generate_custom_inputs_explorer( end_month_abbr = month_names[int(end_month)] if var in mapping_dictionary: var_fullname = "".join( - word.capitalize() for word in mapping_dictionary[var]["name"].split(" ") + word.capitalize() for word in mapping_dictionary[var][ColNames.NAME].split(" ") ) else: var_fullname = var if filter_var in mapping_dictionary: filter_fullname = "".join( word.capitalize() - for word in mapping_dictionary[filter_var]["name"].split(" ") + for word in mapping_dictionary[filter_var][ColNames.NAME].split(" ") ) else: filter_fullname = filter_var @@ -121,14 +121,14 @@ def generate_custom_inputs_psy( if colorby_var in mapping_dictionary: colorby_fullname = "".join( word.capitalize() - for word in mapping_dictionary[colorby_var]["name"].split(" ") + for word in mapping_dictionary[colorby_var][ColNames.NAME].split(" ") ) else: colorby_fullname = colorby_var if data_filter_var in mapping_dictionary: data_filter_fullname = "".join( word.capitalize() - for word in mapping_dictionary[data_filter_var]["name"].split(" ") + for word in mapping_dictionary[data_filter_var][ColNames.NAME].split(" ") ) else: data_filter_fullname = data_filter_var @@ -235,7 +235,7 @@ def summary_table_tmp_rh_tab(df, value, si_ip): df_summary = pd.concat([df_summary, df_sum]) unit = ( - mapping_dictionary[value][si_ip]["unit"] + mapping_dictionary[value][si_ip][ColNames.UNIT] .replace("", "") .replace("", "") ) diff --git a/pages/summary.py b/pages/summary.py index 691fa233..870ccd21 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -224,9 +224,9 @@ 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['city']}, {meta['country']}" - lon = f"Longitude: {meta['lon']}" - lat = f"Latitude: {meta['lat']}" + location = f"Location: {meta[ColNames.CITY]}, {meta[ColNames.COUNTRY]}" + lon = f"Longitude: {meta[ColNames.LON]}" + lat = f"Latitude: {meta[ColNames.LAT]}" site_elevation = float(meta["site_elevation"]) site_elevation = round(site_elevation, 2) @@ -237,12 +237,12 @@ def update_location_info(ts, df, meta, si_ip): else: elevation = f"Elevation above sea level: {meta['site_elevation']} m" period = "" - if meta["period"]: - start, stop = meta["period"].split("-") + 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['lat']}/{meta['lon']}" + f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[ColNames.LAT]}/{meta[ColNames.LON]}" ) climate_text = "" @@ -527,11 +527,11 @@ def download_clima_dataframe(n_clicks, df, meta, si_ip): elif df is not None: if si_ip == UnitSystem.SI: return dcc.send_data_frame( - df.to_csv, f"df_{meta['city']}_{meta['country']}_Clima_SIunit.csv" + df.to_csv, f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_SIunit.csv" ) else: return dcc.send_data_frame( - df.to_csv, f"df_{meta['city']}_{meta['country']}_Clima_IPunit.csv" + df.to_csv, f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_IPunit.csv" ) else: print("df not loaded yet") @@ -552,7 +552,7 @@ def download_epw(n_clicks, meta): lines[0] = lines[0].replace("b'", "") return dict( content="\n".join(lines), - filename=f"{meta['city']}_{meta['country']}.epw", + filename=f"{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}.epw", ) else: raise PreventUpdate From 7c43d2ad287204bb31cf651a823553ac5b2000b6 Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Wed, 27 Aug 2025 15:24:09 +1000 Subject: [PATCH 052/163] Fix: Update the column name in file global_column_names.py, layout.py and template_graphs.py --- pages/lib/global_column_names.py | 4 ++++ pages/lib/layout.py | 10 +++++----- pages/lib/template_graphs.py | 16 ++++++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index dcae7541..c961fa18 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -95,3 +95,7 @@ class ColNames(str, Enum): UNIT = "unit" TWENTY_FOUR_HOUR = "24h" TIMES = "times" + + PATH = "path" + WIND_DIR_BINS = "WindDir_bins" + WIND_SPD_BINS = "WindSpd_bins" \ No newline at end of file diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 558e3745..2ec78075 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -3,7 +3,7 @@ from dash import dcc, html import dash_mantine_components as dmc from dash_iconify import DashIconify - +from pages.lib.global_column_names import ColNames from config import DocLinks, UnitSystem @@ -242,9 +242,9 @@ def build_tabs(): [ dbc.NavItem( dbc.NavLink( - page["name"], - id=page["path"], - href=page["path"], + page[ColNames.NAME], + id=page[ColNames.PATH], + href=page[ColNames.PATH], active="exact", className="nav-link", disabled=True, @@ -252,7 +252,7 @@ def build_tabs(): className="custom-tab", ) for page in dash.page_registry.values() - if page["name"] not in ["404", "changelog"] + if page[ColNames.NAME] not in ["404", "changelog"] ], id="tabs", class_name="tab-container", diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index b0f20f1f..ad7d6249 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -156,9 +156,9 @@ def yearly_profile(df, var, global_local, si_ip): if var == ColNames.DBT: # plot ashrae adaptive comfort limits (80%) - lo80 = df.groupby(ColNames.DOY)["adaptive_cmf_80_low"].mean().values - hi80 = df.groupby(ColNames.DOY)["adaptive_cmf_80_up"].mean().values - rmt = df.groupby(ColNames.DOY)["adaptive_cmf_rmt"].mean().values + lo80 = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_80_LOW].mean().values + hi80 = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_80_UP].mean().values + rmt = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_RMT].mean().values # set color https://github.com/CenterForTheBuiltEnvironment/clima/issues/113 implementation var_bar_colors = np.where((rmt > 40) | (rmt < 10), "lightgray", "darkgray") @@ -175,8 +175,8 @@ def yearly_profile(df, var, global_local, si_ip): ) # plot ashrae adaptive comfort limits (90%) - lo90 = df.groupby(ColNames.DOY)["adaptive_cmf_90_low"].mean().values - hi90 = df.groupby(ColNames.DOY)["adaptive_cmf_90_up"].mean().values + lo90 = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_90_LOW].mean().values + hi90 = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_90_UP].mean().values trace4 = go.Bar( x=df[ColNames.UTC_TIME].dt.date.unique(), @@ -525,12 +525,12 @@ def wind_rose(df, title, month, hour, labels, si_ip): ) # Rename the category in the 'WindDir_bins' column - df_binned["WindDir_bins"] = df_binned["WindDir_bins"].rename({360.0: 0.0}) + df_binned[ColNames.WIND_DIR_BINS] = df_binned[ColNames.WIND_DIR_BINS].rename({360.0: 0.0}) rose = ( - df_binned.groupby(by=["WindSpd_bins", "WindDir_bins"], observed=False) + df_binned.groupby(by=[ColNames.WIND_SPD_BINS, ColNames.WIND_DIR_BINS], observed=False) .size() - .unstack(level="WindSpd_bins") + .unstack(level=ColNames.WIND_SPD_BINS) .fillna(0) .assign(calm=lambda d: calm_count / d.shape[0]) .sort_index(axis=1) From 49d12c95784345b74c415d0f8639b44402549b76 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Wed, 27 Aug 2025 15:45:50 +1000 Subject: [PATCH 053/163] fix: replaced the string with the constants --- pages/lib/global_column_names.py | 10 +++++++++- pages/lib/utils.py | 10 +++++----- pages/select.py | 12 ++++++------ pages/summary.py | 6 +++--- 4 files changed, 23 insertions(+), 15 deletions(-) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index c961fa18..86ebb458 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -97,5 +97,13 @@ class ColNames(str, Enum): TIMES = "times" PATH = "path" + FILE_NAME = "filename" WIND_DIR_BINS = "WindDir_bins" - WIND_SPD_BINS = "WindSpd_bins" \ No newline at end of file + WIND_SPD_BINS = "WindSpd_bins" + + TO_IMAGE_BUTTON_OPTIONS = "toImageButtonOptions" + INVERT = "invert" + FEATURES = "features" + GEOMETRY_COORDINATES = "geometry.coordinates" + PROP_ID = "prop_id" + SITE_ELEVATION = "site_elevation" \ No newline at end of file diff --git a/pages/lib/utils.py b/pages/lib/utils.py index b40e39a8..7bc45ef3 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -34,10 +34,10 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): if units: custom_str += f"_{units}" if meta: - file_name = f"{meta['city']}_{meta['country']}_{tab_name}{custom_str}" - figure_config["toImageButtonOptions"]["filename"] = file_name + file_name = f"{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_{tab_name}{custom_str}" + figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = file_name else: - figure_config["toImageButtonOptions"]["filename"] = f"{tab_name}{custom_str}" + figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = f"{tab_name}{custom_str}" return figure_config @@ -260,10 +260,10 @@ def summary_table_tmp_rh_tab(df, value, si_ip): def determine_month_and_hour_filter(month, hour, invert_month, invert_hour): start_month, end_month = month - if invert_month == ["invert"] and (start_month != 1 or end_month != 12): + if invert_month == [ColNames.INVERT] and (start_month != 1 or end_month != 12): end_month, start_month = month start_hour, end_hour = hour - if invert_hour == ["invert"] and (start_hour != 0 or end_hour != 24): + if invert_hour == [ColNames.INVERT] and (start_hour != 0 or end_hour != 24): end_hour, start_hour = hour return start_month, end_month, start_hour, end_hour diff --git a/pages/select.py b/pages/select.py index b4b202cb..714434d9 100644 --- a/pages/select.py +++ b/pages/select.py @@ -140,7 +140,7 @@ def submitted_data( """Process the uploaded file or download the EPW from the URL""" ctx = dash.callback_context - if ctx.triggered[0]["prop_id"] == "modal-yes-button.n_clicks": + if ctx.triggered[0][ColNames.PROP_ID] == "modal-yes-button.n_clicks": lines = get_data(url_store) if lines is None: return ( @@ -162,7 +162,7 @@ def submitted_data( ) elif ( - ctx.triggered[0]["prop_id"] == "upload-data.contents" + ctx.triggered[0][ColNames.PROP_ID] == "upload-data.contents" and list_of_contents is not None ): content_type, content_string = list_of_contents[0].split(",") @@ -275,7 +275,7 @@ def enable_tabs_when_data_is_loaded(meta, data): False, False, False, - "Current Location: " + meta["city"] + ", " + meta["country"], + "Current Location: " + meta[ColNames.CITY] + ", " + meta[ColNames.COUNTRY], ) @@ -330,9 +330,9 @@ def plot_location_epw_files(pathname): with open("./assets/data/epw_location.json", encoding="utf8") as data_file: data = json.load(data_file) - df = json_normalize(data["features"]) - df[["lon", "lat"]] = pd.DataFrame(df["geometry.coordinates"].tolist()) - df["lat"] += 0.010 + df = json_normalize(data[ColNames.FEATURES]) + df[[ColNames.LON, ColNames.LAT]] = pd.DataFrame(df[ColNames.GEOMETRY_COORDINATES].tolist()) + df[ColNames.LAT] += 0.010 df = df.rename(columns={"properties.epw": "Source"}) fig = px.scatter_mapbox( diff --git a/pages/summary.py b/pages/summary.py index 870ccd21..efaa9235 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -228,14 +228,14 @@ def update_location_info(ts, df, meta, si_ip): lon = f"Longitude: {meta[ColNames.LON]}" lat = f"Latitude: {meta[ColNames.LAT]}" - site_elevation = float(meta["site_elevation"]) + site_elevation = float(meta[ColNames.SITE_ELEVATION.SITE_ELEVATION]) site_elevation = round(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" else: - elevation = f"Elevation above sea level: {meta['site_elevation']} m" + elevation = f"Elevation above sea level: {meta[ColNames.SITE_ELEVATION]} m" period = "" if meta[ColNames.PERIOD]: start, stop = meta[ColNames.PERIOD].split("-") @@ -333,7 +333,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ ctx = dash.callback_context if ( - ctx.triggered[0]["prop_id"] == "submit-set-points.n_clicks_timestamp" + ctx.triggered[0][ColNames.PROP_ID] == "submit-set-points.n_clicks_timestamp" or n_clicks is None ): hdd_setpoint = hdd_value From 81b2ec8048ce855a6db2512fb51ba7e253f3926e Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Wed, 27 Aug 2025 16:15:59 +1000 Subject: [PATCH 054/163] fix: replaced the string with the constants in outdoor.py --- pages/outdoor.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pages/outdoor.py b/pages/outdoor.py index 632256a6..788856ff 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -7,6 +7,7 @@ from config import PageUrls, DocLinks, PageInfo from pages.lib.global_element_ids import ElementIds +from pages.lib.global_column_names import ColNames from pages.lib.global_scheme import ( outdoor_dropdown_names, ) @@ -235,10 +236,10 @@ def update_outdoor_comfort_output(_, df): Description of the best weather condition(s). """ cols = [ - "utci_noSun_Wind_categories", - "utci_noSun_noWind_categories", - "utci_Sun_Wind_categories", - "utci_Sun_noWind_categories", + ColNames.UTCI_NOSUN_WIND_CATEGORIES, + ColNames.UTCI_NOSUN_NOWIND_CATEGORIES, + ColNames.UTCI_SUN_WIND_CATEGORIES, + ColNames.UTCI_SUN_NOWIND_CATEGORIES, ] cols_with_the_highest_number_of_zero = [] highest_count = 0 From f3dca83283ce9b88d932640f287a448ee4a56efb Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 28 Aug 2025 12:22:46 +1000 Subject: [PATCH 055/163] style: ruff format Refactor multiple files to enhance code clarity by adjusting line breaks, spacing, and indentation. This includes consistent formatting in function calls and variable assignments across various modules. --- pages/explorer.py | 4 +- pages/lib/charts_summary.py | 1 + pages/lib/extract_df.py | 63 +++++++++++++++++++++++++------- pages/lib/global_column_names.py | 15 +++++--- pages/lib/global_scheme.py | 10 +++-- pages/lib/template_graphs.py | 8 +++- pages/lib/utils.py | 15 ++++++-- pages/outdoor.py | 4 +- pages/psy-chart.py | 16 +++++--- pages/select.py | 4 +- pages/summary.py | 13 +++++-- pages/sun.py | 21 ++++++----- pages/t_rh.py | 51 +++++++++++++++++++++++--- pages/wind.py | 62 ++++++++++++++++++++++--------- 14 files changed, 215 insertions(+), 72 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index 32ae86db..8573edb3 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -953,5 +953,7 @@ def update_table( & (df[ColNames.HOUR] <= end_hour) ] return summary_table_tmp_rh_tab( - filtered_df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], dd_value, si_ip + filtered_df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], + dd_value, + si_ip, ) diff --git a/pages/lib/charts_summary.py b/pages/lib/charts_summary.py index 6973564d..d48a0a51 100644 --- a/pages/lib/charts_summary.py +++ b/pages/lib/charts_summary.py @@ -2,6 +2,7 @@ import plotly.express as px from pages.lib.global_column_names import ColNames + def world_map(meta): """Return the world map showing the current location.""" latitude = float(meta[ColNames.LAT]) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 451e1b8d..cb4153a3 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -68,7 +68,9 @@ def get_location_info(lst, file_name): # from OneClimaBuilding files extract info about reference years try: - location_info[ColNames.PERIOD] = re.search(r'cord=[\'"]?([^\'" >]+);', lst[5]).group(1) + location_info[ColNames.PERIOD] = re.search( + r'cord=[\'"]?([^\'" >]+);', lst[5] + ).group(1) except AttributeError: pass @@ -94,7 +96,9 @@ def create_df(lst, file_name): # from OneClimaBuilding files extract info about reference years try: - location_info[ColNames.PERIOD] = re.search(r'cord=[\'"]?([^\'" >]+);', lst[5]).group(1) + location_info[ColNames.PERIOD] = re.search( + r'cord=[\'"]?([^\'" >]+);', lst[5] + ).group(1) except AttributeError: pass @@ -165,7 +169,9 @@ def create_df(lst, file_name): # Add in month names month_look_up = {ix + 1: month for ix, month in enumerate(month_lst)} - epw_df[ColNames.MONTH_NAMES] = epw_df[ColNames.MONTH].astype("int").map(month_look_up) + epw_df[ColNames.MONTH_NAMES] = ( + epw_df[ColNames.MONTH].astype("int").map(month_look_up) + ) # Change to int type epw_df[[ColNames.YEAR, ColNames.DAY, ColNames.MONTH, ColNames.HOUR]] = epw_df[ @@ -173,10 +179,17 @@ def create_df(lst, file_name): ].astype(int) # Add in DOY - df_doy = epw_df.groupby([ColNames.MONTH, ColNames.DAY])[ColNames.HOUR].count().reset_index() + df_doy = ( + epw_df.groupby([ColNames.MONTH, ColNames.DAY])[ColNames.HOUR] + .count() + .reset_index() + ) df_doy[ColNames.DOY] = df_doy.index + 1 epw_df = pd.merge( - epw_df, df_doy[[ColNames.MONTH, ColNames.DAY, ColNames.DOY]], on=[ColNames.MONTH, ColNames.DAY], how="left" + epw_df, + df_doy[[ColNames.MONTH, ColNames.DAY, ColNames.DOY]], + on=[ColNames.MONTH, ColNames.DAY], + how="left", ) change_to_float = [ @@ -249,7 +262,9 @@ def create_df(lst, file_name): floor_reflectance, ) mrt_df = pd.DataFrame.from_records(mrt) - mrt_df[ColNames.DELTA_MRT] = mrt_df[ColNames.DELTA_MRT].mask(mrt_df[ColNames.DELTA_MRT] >= 70, 70) + mrt_df[ColNames.DELTA_MRT] = mrt_df[ColNames.DELTA_MRT].mask( + mrt_df[ColNames.DELTA_MRT] >= 70, 70 + ) mrt_df = mrt_df.set_index(epw_df.times) epw_df = epw_df.join(mrt_df) @@ -266,16 +281,28 @@ def create_df(lst, file_name): epw_df[ColNames.WIND_SPEED_UTCI] >= 0, 0.5 ) epw_df[ColNames.UTCI_NO_SUN_WIND] = utci( - epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df[ColNames.WIND_SPEED_UTCI], epw_df[ColNames.RH] + epw_df[ColNames.DBT], + epw_df[ColNames.DBT], + epw_df[ColNames.WIND_SPEED_UTCI], + epw_df[ColNames.RH], ) epw_df[ColNames.UTCI_NO_SUN_NO_WIND] = utci( - epw_df[ColNames.DBT], epw_df[ColNames.DBT], epw_df[ColNames.WIND_SPEED_UTCI_0], epw_df[ColNames.RH] + epw_df[ColNames.DBT], + epw_df[ColNames.DBT], + epw_df[ColNames.WIND_SPEED_UTCI_0], + epw_df[ColNames.RH], ) epw_df[ColNames.UTCI_SUN_WIND] = utci( - epw_df[ColNames.DBT], epw_df[ColNames.MRT], epw_df[ColNames.WIND_SPEED_UTCI], epw_df[ColNames.RH] + epw_df[ColNames.DBT], + epw_df[ColNames.MRT], + epw_df[ColNames.WIND_SPEED_UTCI], + epw_df[ColNames.RH], ) epw_df[ColNames.UTCI_SUN_NO_WIND] = utci( - epw_df[ColNames.DBT], epw_df[ColNames.MRT], epw_df[ColNames.WIND_SPEED_UTCI_0], epw_df[ColNames.RH] + epw_df[ColNames.DBT], + epw_df[ColNames.MRT], + epw_df[ColNames.WIND_SPEED_UTCI_0], + epw_df[ColNames.RH], ) utci_bins = [-999, -40, -27, -13, 0, 9, 26, 32, 38, 46, 999] @@ -330,10 +357,18 @@ def create_df(lst, file_name): ) epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_RMT] = rmt epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_COMFORT] = r[ColNames.TMP_CMF] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_LOW] = r[ColNames.TMP_CMF_80_LOW] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_UP] = r[ColNames.TMP_CMF_80_UP] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_LOW] = r[ColNames.TMP_CMF_90_LOW] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_UP] = r[ColNames.TMP_CMF_90_UP] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_LOW] = r[ + ColNames.TMP_CMF_80_LOW + ] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_UP] = r[ + ColNames.TMP_CMF_80_UP + ] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_LOW] = r[ + ColNames.TMP_CMF_90_LOW + ] + epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_UP] = r[ + ColNames.TMP_CMF_90_UP + ] return epw_df, location_info diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index 86ebb458..e88430c3 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -65,9 +65,15 @@ class ColNames(str, Enum): UTCI_NO_SUN_WIND = "utci_noSun_Wind" # Utci no Sun Wind UTCI_NO_SUN_NO_WIND = "utci_noSun_noWind" # Utci no Sun no Wind UTCI_SUN_WIND_CATEGORIES = "utci_Sun_Wind_categories" # Utci Sun Wind Categories - UTCI_SUN_NOWIND_CATEGORIES = "utci_Sun_noWind_categories" # Utci Sun no Wind Categories - UTCI_NOSUN_WIND_CATEGORIES = "utci_noSun_Wind_categories" # Utci no Sun Wind Categories - UTCI_NOSUN_NOWIND_CATEGORIES = "utci_noSun_noWind_categories" # Utci no Sun no Wind Categories + UTCI_SUN_NOWIND_CATEGORIES = ( + "utci_Sun_noWind_categories" # Utci Sun no Wind Categories + ) + UTCI_NOSUN_WIND_CATEGORIES = ( + "utci_noSun_Wind_categories" # Utci no Sun Wind Categories + ) + UTCI_NOSUN_NOWIND_CATEGORIES = ( + "utci_noSun_noWind_categories" # Utci no Sun no Wind Categories + ) ADAPTIVE_COMFORT = "adaptive_comfort" # Adaptive comfort ADAPTIVE_CMF_80_LOW = "adaptive_cmf_80_low" # Adaptive comfort 80 low ADAPTIVE_CMF_80_UP = "adaptive_cmf_80_up" # Adaptive comfort 80 up @@ -82,7 +88,6 @@ class ColNames(str, Enum): TMP_CMF_90_UP = "tmp_cmf_90_up" CONVERSION_FUNCTION = "conversion_function" - # ==================== Calculation column ==================== FAKE_YEAR = "fake_year" # Fake Year MONTH_NAMES = "month_names" # Month names @@ -106,4 +111,4 @@ class ColNames(str, Enum): FEATURES = "features" GEOMETRY_COORDINATES = "geometry.coordinates" PROP_ID = "prop_id" - SITE_ELEVATION = "site_elevation" \ No newline at end of file + SITE_ELEVATION = "site_elevation" diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index 2f984aac..51d51a0d 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -901,13 +901,17 @@ ] sun_cloud_tab_dropdown_names = { - mapping_dictionary[key][ColNames.NAME]: key for key in variables_sun_cloud_tab_dropdown + mapping_dictionary[key][ColNames.NAME]: key + for key in variables_sun_cloud_tab_dropdown } -dropdown_names = {mapping_dictionary[key][ColNames.NAME]: key for key in variables_dropdown} +dropdown_names = { + mapping_dictionary[key][ColNames.NAME]: key for key in variables_dropdown +} more_variables_dropdown = { - mapping_dictionary[key][ColNames.NAME]: key for key in variables_more_variables_dropdown + mapping_dictionary[key][ColNames.NAME]: key + for key in variables_more_variables_dropdown } sun_cloud_tab_explore_dropdown_names = { diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index ad7d6249..e16ef192 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -525,10 +525,14 @@ def wind_rose(df, title, month, hour, labels, si_ip): ) # Rename the category in the 'WindDir_bins' column - df_binned[ColNames.WIND_DIR_BINS] = df_binned[ColNames.WIND_DIR_BINS].rename({360.0: 0.0}) + df_binned[ColNames.WIND_DIR_BINS] = df_binned[ColNames.WIND_DIR_BINS].rename( + {360.0: 0.0} + ) rose = ( - df_binned.groupby(by=[ColNames.WIND_SPD_BINS, ColNames.WIND_DIR_BINS], observed=False) + df_binned.groupby( + by=[ColNames.WIND_SPD_BINS, ColNames.WIND_DIR_BINS], observed=False + ) .size() .unstack(level=ColNames.WIND_SPD_BINS) .fillna(0) diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 7bc45ef3..2455b144 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -34,10 +34,14 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): if units: custom_str += f"_{units}" if meta: - file_name = f"{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_{tab_name}{custom_str}" + file_name = ( + f"{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_{tab_name}{custom_str}" + ) figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = file_name else: - figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = f"{tab_name}{custom_str}" + figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = ( + f"{tab_name}{custom_str}" + ) return figure_config @@ -90,7 +94,8 @@ def generate_custom_inputs_explorer( end_month_abbr = month_names[int(end_month)] if var in mapping_dictionary: var_fullname = "".join( - word.capitalize() for word in mapping_dictionary[var][ColNames.NAME].split(" ") + word.capitalize() + for word in mapping_dictionary[var][ColNames.NAME].split(" ") ) else: var_fullname = var @@ -241,7 +246,9 @@ def summary_table_tmp_rh_tab(df, value, si_ip): ) return dash_table.DataTable( columns=[ - {"name": i, "id": i} if i == ColNames.MONTH else {"name": f"{i} ({unit})", "id": i} + {"name": i, "id": i} + if i == ColNames.MONTH + else {"name": f"{i} ({unit})", "id": i} for i in df_summary.columns ], style_table={"overflowX": "auto"}, diff --git a/pages/outdoor.py b/pages/outdoor.py index 788856ff..04380c79 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -54,7 +54,9 @@ def inputs_outdoor_comfort(): options=outdoor_dropdown_names, value="utci_Sun_Wind", ), - html.Div(id=ElementIds.IMAGE_SELECTION, style={"flex": "10%"}), + html.Div( + id=ElementIds.IMAGE_SELECTION, style={"flex": "10%"} + ), ], ), ], diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 70c137c1..54e51ee6 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -173,7 +173,7 @@ def inputs(): children=["Filter Variable:"], style={"flex": "30%"} ), dropdown( - id=ElementIds.PSY_VAR_DROPDOWN , + id=ElementIds.PSY_VAR_DROPDOWN, options=dropdown_names, value=ColNames.RH, style={"flex": "70%"}, @@ -249,7 +249,7 @@ def layout(): State(ElementIds.PSY_HOUR_SLIDER, "value"), State(ElementIds.PSY_MIN_VAL, "value"), State(ElementIds.PSY_MAX_VAL, "value"), - State(ElementIds.PSY_VAR_DROPDOWN , "value"), + State(ElementIds.PSY_VAR_DROPDOWN, "value"), State(ElementIds.ID_PSY_CHART_META_STORE, "data"), State(ElementIds.INVERT_MONTH_PSY, "value"), State(ElementIds.INVERT_HOUR_PSY, "value"), @@ -451,7 +451,9 @@ def update_psych_chart( colorscale=var_color, colorbar=var_colorbar, ), - customdata=np.stack((df[ColNames.RH], df["h"], df[var], df["t_dp"]), axis=-1), + customdata=np.stack( + (df[ColNames.RH], df["h"], df[var], df["t_dp"]), axis=-1 + ), hovertemplate=mapping_dictionary[ColNames.DBT][ColNames.NAME] + ": %{x:.2f}" + mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT] @@ -476,8 +478,12 @@ def update_psych_chart( ) ) - xtitle_name = "Temperature" + " " + mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT] - ytitle_name = "Humidity Ratio" + " " + mapping_dictionary[ColNames.HR][si_ip][ColNames.UNIT] + xtitle_name = ( + "Temperature" + " " + mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT] + ) + ytitle_name = ( + "Humidity Ratio" + " " + mapping_dictionary[ColNames.HR][si_ip][ColNames.UNIT] + ) fig.update_layout(template=template, margin=tight_margins) fig.update_xaxes( title_text=xtitle_name, diff --git a/pages/select.py b/pages/select.py index 714434d9..65805cc4 100644 --- a/pages/select.py +++ b/pages/select.py @@ -331,7 +331,9 @@ def plot_location_epw_files(pathname): data = json.load(data_file) df = json_normalize(data[ColNames.FEATURES]) - df[[ColNames.LON, ColNames.LAT]] = pd.DataFrame(df[ColNames.GEOMETRY_COORDINATES].tolist()) + df[[ColNames.LON, ColNames.LAT]] = pd.DataFrame( + df[ColNames.GEOMETRY_COORDINATES].tolist() + ) df[ColNames.LAT] += 0.010 df = df.rename(columns={"properties.epw": "Source"}) diff --git a/pages/summary.py b/pages/summary.py index efaa9235..03a07843 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -43,7 +43,8 @@ def layout(): @callback( - Output(ElementIds.TAB_TWO_CONTAINER, "children"), [Input(ElementIds.ID_SUMMARY_SI_IP_RADIO_INPUT, "value")] + Output(ElementIds.TAB_TWO_CONTAINER, "children"), + [Input(ElementIds.ID_SUMMARY_SI_IP_RADIO_INPUT, "value")], ) def update_layout(si_ip): if si_ip == UnitSystem.SI: @@ -260,7 +261,9 @@ def update_location_info(ts, df, meta, si_ip): # 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] + ) 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() @@ -527,11 +530,13 @@ def download_clima_dataframe(n_clicks, df, meta, si_ip): elif df is not None: if si_ip == UnitSystem.SI: return dcc.send_data_frame( - df.to_csv, f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_SIunit.csv" + df.to_csv, + f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_SIunit.csv", ) else: return dcc.send_data_frame( - df.to_csv, f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_IPunit.csv" + df.to_csv, + f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_IPunit.csv", ) else: print("df not loaded yet") diff --git a/pages/sun.py b/pages/sun.py index b6663e76..9d6a49bc 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -72,7 +72,7 @@ def sun_path(): style={"width": "10rem"}, ), dropdown( - id= ElementIds.CUSTOM_SUN_VIEW_DROPDOWN, + id=ElementIds.CUSTOM_SUN_VIEW_DROPDOWN, options={ "Spherical": "polar", "Cartesian": "cartesian", @@ -92,7 +92,7 @@ def sun_path(): style={"width": "10rem"}, ), dropdown( - id= ElementIds.CUSTOM_SUN_VAR_DROPDOWN, + id=ElementIds.CUSTOM_SUN_VAR_DROPDOWN, options=sc_dropdown_names, value="None", style={"width": "20rem"}, @@ -102,7 +102,7 @@ def sun_path(): dcc.Loading( type="circle", children=html.Div( - id= ElementIds.CUSTOM_SUNPATH, + id=ElementIds.CUSTOM_SUNPATH, ), ), ], @@ -137,7 +137,7 @@ def explore_daily_heatmap(): ), ], ), - dcc.Loading(type="circle", children=html.Div(id= ElementIds.TAB4_DAILY)), + dcc.Loading(type="circle", children=html.Div(id=ElementIds.TAB4_DAILY)), dcc.Loading( type="circle", children=html.Div(id=ElementIds.TAB4_HEATMAP), @@ -148,7 +148,7 @@ def explore_daily_heatmap(): def static_section(): return html.Div( - id= ElementIds.STATIC_SECTION, + id=ElementIds.STATIC_SECTION, className="container-col full-width", children=[ # ... @@ -160,12 +160,15 @@ def layout(): """Contents of tab four.""" return html.Div( className="container-col", - id= ElementIds.TAB_FOUR_CONTAINER, + 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")]) +@callback( + Output(ElementIds.STATIC_SECTION, "children"), + [Input(ElementIds.ID_SUN_SI_IP_RADIO_INPUT, "value")], +) def update_static_section(si_ip): hor_unit = "Wh/m²" if si_ip == UnitSystem.IP: @@ -180,7 +183,7 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id= ElementIds.MONTHLY_SOLAR), + children=html.Div(id=ElementIds.MONTHLY_SOLAR), ), html.Div( children=title_with_link( @@ -191,7 +194,7 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id= ElementIds.CLOUD_COVER), + children=html.Div(id=ElementIds.CLOUD_COVER), ), ] diff --git a/pages/t_rh.py b/pages/t_rh.py index 12d89fba..7aff5282 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -148,7 +148,16 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("DryBulbTemperature_daily", meta, units), figure=daily_profile( - df[[ColNames.DBT, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY, ColNames.MONTH]], + df[ + [ + ColNames.DBT, + ColNames.HOUR, + ColNames.UTC_TIME, + ColNames.MONTH_NAMES, + ColNames.DAY, + ColNames.MONTH, + ] + ], ColNames.DBT, global_local, si_ip, @@ -159,7 +168,16 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("RelativeHumidity_daily", meta, units), figure=daily_profile( - df[[ColNames.RH, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY, ColNames.MONTH]], + df[ + [ + ColNames.RH, + ColNames.HOUR, + ColNames.UTC_TIME, + ColNames.MONTH_NAMES, + ColNames.DAY, + ColNames.MONTH, + ] + ], ColNames.RH, global_local, si_ip, @@ -187,7 +205,15 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("DryBulbTemperature_heatmap", meta, units), figure=heatmap( - df[[ColNames.DBT, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY]], + df[ + [ + ColNames.DBT, + ColNames.HOUR, + ColNames.UTC_TIME, + ColNames.MONTH_NAMES, + ColNames.DAY, + ] + ], ColNames.DBT, global_local, si_ip, @@ -198,7 +224,15 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): return dcc.Graph( config=generate_chart_name("RelativeHumidity_heatmap", meta, units), figure=heatmap( - df[[ColNames.RH, ColNames.HOUR, ColNames.UTC_TIME, ColNames.MONTH_NAMES, ColNames.DAY]], + df[ + [ + ColNames.RH, + ColNames.HOUR, + ColNames.UTC_TIME, + ColNames.MONTH_NAMES, + ColNames.DAY, + ] + ], ColNames.RH, global_local, si_ip, @@ -212,10 +246,15 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), Input(ElementIds.ID_T_RH_DROPDOWN, "value"), ], - [State(ElementIds.ID_T_RH_DF_STORE, "data"), State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data")], + [ + State(ElementIds.ID_T_RH_DF_STORE, "data"), + State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), + ], ) def update_table(_, dd_value, df, si_ip): """Update the contents of tab three. Passing in general info (df, meta).""" return summary_table_tmp_rh_tab( - df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], dd_value, si_ip + df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], + dd_value, + si_ip, ) diff --git a/pages/wind.py b/pages/wind.py index 92cc1284..e917c9b3 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -93,7 +93,8 @@ def seasonal_wind_rose(): ), ), html.P( - className="seasonal-text", id=ElementIds.WINTER_WIND_ROSE_TEXT + className="seasonal-text", + id=ElementIds.WINTER_WIND_ROSE_TEXT, ), ], ), @@ -108,7 +109,8 @@ def seasonal_wind_rose(): ), ), html.P( - className="seasonal-text", id=ElementIds.SPRING_WIND_ROSE_TEXT + className="seasonal-text", + id=ElementIds.SPRING_WIND_ROSE_TEXT, ), ], ), @@ -123,12 +125,13 @@ def seasonal_wind_rose(): dcc.Loading( type="circle", children=html.Div( - id= ElementIds.SUMMER_WIND_ROSE, + id=ElementIds.SUMMER_WIND_ROSE, className="daily-wind-graph", ), ), html.P( - className="seasonal-text", id=ElementIds.SUMMER_WIND_ROSE_TEXT + className="seasonal-text", + id=ElementIds.SUMMER_WIND_ROSE_TEXT, ), ], ), @@ -142,7 +145,10 @@ def seasonal_wind_rose(): className="daily-wind-graph", ), ), - html.P(className="seasonal-text", id=ElementIds.FALL_WIND_ROSE_TEXT), + html.P( + className="seasonal-text", + id=ElementIds.FALL_WIND_ROSE_TEXT, + ), ], ), ], @@ -180,7 +186,10 @@ def daily_wind_rose(): ), ), ), - html.P(className="daily-text", id=ElementIds.MORNING_WIND_ROSE_TEXT), + html.P( + className="daily-text", + id=ElementIds.MORNING_WIND_ROSE_TEXT, + ), ], ), html.Div( @@ -195,7 +204,10 @@ def daily_wind_rose(): ), ), ), - html.P(className="daily-text", id=ElementIds.NOON_WIND_ROSE_TEXT), + html.P( + className="daily-text", + id=ElementIds.NOON_WIND_ROSE_TEXT, + ), ], ), html.Div( @@ -210,7 +222,10 @@ def daily_wind_rose(): ), ), ), - html.P(className="daily-text", id=ElementIds.NIGHT_WIND_ROSE_TEXT), + html.P( + className="daily-text", + id=ElementIds.NIGHT_WIND_ROSE_TEXT, + ), ], ), ], @@ -454,9 +469,13 @@ def update_custom_wind_rose( # Wind Rose Graphs if start_month <= end_month: - df = df.loc[(df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month)] + df = df.loc[ + (df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month) + ] else: - df = df.loc[(df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month)] + df = df.loc[ + (df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month) + ] if start_hour <= end_hour: df = df.loc[(df[ColNames.HOUR] >= start_hour) & (df[ColNames.HOUR] <= end_hour)] else: @@ -509,25 +528,30 @@ def update_seasonal_graphs(_, df, meta, si_ip): # Text winter_df = df.loc[ - (df[ColNames.MONTH] <= winter_months[1]) | (df[ColNames.MONTH] >= winter_months[0]) + (df[ColNames.MONTH] <= winter_months[1]) + | (df[ColNames.MONTH] >= winter_months[0]) ] query_calm_wind = "wind_speed == 0" winter_total_count = winter_df.shape[0] winter_calm_count = winter_df.query(query_calm_wind).shape[0] spring_df = df.loc[ - (df[ColNames.MONTH] >= spring_months[0]) & (df[ColNames.MONTH] <= spring_months[1]) + (df[ColNames.MONTH] >= spring_months[0]) + & (df[ColNames.MONTH] <= spring_months[1]) ] spring_total_count = spring_df.shape[0] spring_calm_count = spring_df.query(query_calm_wind).shape[0] summer_df = df.loc[ - (df[ColNames.MONTH] >= summer_months[0]) & (df[ColNames.MONTH] <= summer_months[1]) + (df[ColNames.MONTH] >= summer_months[0]) + & (df[ColNames.MONTH] <= summer_months[1]) ] summer_total_count = summer_df.shape[0] summer_calm_count = summer_df.query(query_calm_wind).shape[0] - fall_df = df.loc[(df[ColNames.MONTH] >= fall_months[0]) & (df[ColNames.MONTH] <= fall_months[1])] + fall_df = df.loc[ + (df[ColNames.MONTH] >= fall_months[0]) & (df[ColNames.MONTH] <= fall_months[1]) + ] fall_total_count = fall_df.shape[0] fall_calm_count = fall_df.query(query_calm_wind).shape[0] @@ -623,18 +647,22 @@ def update_daily_graphs(_, df, meta, si_ip): # Text query_calm_wind = "wind_speed == 0" morning_df = df.loc[ - (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1]) + (df[ColNames.HOUR] >= morning_times[0]) + & (df[ColNames.HOUR] <= morning_times[1]) ] morning_total_count = morning_df.shape[0] morning_calm_count = morning_df.query(query_calm_wind).shape[0] noon_df = df.loc[ - (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1]) + (df[ColNames.HOUR] >= morning_times[0]) + & (df[ColNames.HOUR] <= morning_times[1]) ] noon_total_count = noon_df.shape[0] noon_calm_count = noon_df.query(query_calm_wind).shape[0] - night_df = df.loc[(df[ColNames.HOUR] <= night_times[1]) | (df[ColNames.HOUR] >= night_times[0])] + night_df = df.loc[ + (df[ColNames.HOUR] <= night_times[1]) | (df[ColNames.HOUR] >= night_times[0]) + ] night_total_count = night_df.shape[0] night_calm_count = night_df.query(query_calm_wind).shape[0] From 02cd0adcb72c6d0a85e7840fbefbc963fe386674 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 28 Aug 2025 12:30:14 +1000 Subject: [PATCH 056/163] fix(layout): Correct label text for yearly chart Update the label text from "Yearly_chart" to "Yearly Chart" for better readability and consistency in the user interface. --- pages/t_rh.py | 2 +- pages/wind.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/pages/t_rh.py b/pages/t_rh.py index 7aff5282..92564b67 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -50,7 +50,7 @@ def layout(): children=[ html.Div( children=title_with_link( - text="Yearly_chart", + text="Yearly Chart", id_button="yearly-chart-label", doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), diff --git a/pages/wind.py b/pages/wind.py index e917c9b3..23975ff6 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -75,7 +75,6 @@ def seasonal_wind_rose(): html.Div( children=title_with_link( text="Seasonal Wind Rose", - id_button="seasonal-rose-chart", doc_link=DocLinks.WIND_ROSE, ), ), From 97fbd56d98a89425768154ecec43a745787b075e Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:30:39 +1000 Subject: [PATCH 057/163] fix: updated the mistake for using constant --- pages/summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/summary.py b/pages/summary.py index efaa9235..f420bc67 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -228,7 +228,7 @@ def update_location_info(ts, df, meta, si_ip): lon = f"Longitude: {meta[ColNames.LON]}" lat = f"Latitude: {meta[ColNames.LAT]}" - site_elevation = float(meta[ColNames.SITE_ELEVATION.SITE_ELEVATION]) + site_elevation = float(meta[ColNames.SITE_ELEVATION]) site_elevation = round(site_elevation, 2) if si_ip != UnitSystem.SI: site_elevation = site_elevation * 3.281 From 0c5ff18ca8e50f32fe7e775b00dfc72cdee4c5ad Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Thu, 28 Aug 2025 12:43:41 +1000 Subject: [PATCH 058/163] feat(global): Add seasonal wind rose document ID Introduce a new constant for the seasonal wind rose document ID in global_element_ids.py and update the wind.py to utilize this ID for the corresponding button. --- pages/lib/global_element_ids.py | 1 + pages/wind.py | 1 + 2 files changed, 2 insertions(+) diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py index d36ff4f7..041d5ae7 100644 --- a/pages/lib/global_element_ids.py +++ b/pages/lib/global_element_ids.py @@ -155,6 +155,7 @@ 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" diff --git a/pages/wind.py b/pages/wind.py index 23975ff6..711f32b5 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -75,6 +75,7 @@ def seasonal_wind_rose(): html.Div( children=title_with_link( text="Seasonal Wind Rose", + id_button=ElementIds.SEASONAL_WIND_ROSE_DOC, doc_link=DocLinks.WIND_ROSE, ), ), From f62dfb4d8c0c80ddfefe99255db1f1044f0f7fa8 Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Fri, 29 Aug 2025 16:04:24 +1000 Subject: [PATCH 059/163] Fix: Add a new file called global_id_buttons.py --- pages/lib/global_id_buttons.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 pages/lib/global_id_buttons.py diff --git a/pages/lib/global_id_buttons.py b/pages/lib/global_id_buttons.py new file mode 100644 index 00000000..89807520 --- /dev/null +++ b/pages/lib/global_id_buttons.py @@ -0,0 +1,26 @@ +class IdButtons: + TABLE_EXPLORE = "table-explore" + NV_NORMALIZE = "nv_normalize" + OUTDOOR_COMFORT_NORMALIZE = "outdoor-comfort-normalize" + TABLE_TMP_RH = "table-tmp-rh" + + DAILY_ROSE_CHART = "daily-rose-chart" + CLIMATE_PROFILES_CHART = "climate-profiles-chart" + PSYCHROMETRIC_CHART_CHART = "Psychrometric-Chart-chart" + + MORE_CHARTS_LABEL = "more-charts-label" + DOWNLOAD_BUTTON_LABEL = "download-button-label" + CUSTOM_HEATMAP_CHART_LABEL = "custom-heatmap-chart-label" + + EXPLORE_YEARLY_CHART_LABEL = "explore-yearly-chart-label" + EXPLORE_DAILY_CHART_LABEL = "explore-daily-chart-label" + EXPLORE_HEATMAP_CHART_LABEL = "explore-heatmap-chart-label" + NATURAL_VENTILATION_LABEL = "natural-ventilation-label" + UTCI_CHARTS_LABEL = "utci-charts-label" + SUN_PATH_CHART_LABEL = "sun-path-chart-label" + DAILY_CHART_LABEL = "daily-chart-label" + MONTHLY_CHART_LABEL = "monthly-chart-label" + CLOUD_CHART_LABEL = "cloud-chart-label" + YEARLY_CHART_LABEL = "yearly-chart-label" + HEATMAP_CHART_LABEL = "heatmap-chart-label" + WIND_ROSE_LABEL = "wind-rose-label" \ No newline at end of file From 530037becfc84c44d3bca86cf786ff65f9ae4e56 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Fri, 29 Aug 2025 18:38:25 +1000 Subject: [PATCH 060/163] fix(layout): fixed the class name to avoid can not load Normalize data chart --- pages/lib/global_column_names.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index e88430c3..589e0483 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -1,7 +1,7 @@ from enum import Enum -class ColNames(str, Enum): +class ColNames: # ==================== Time related column ==================== YEAR = "year" # year PERIOD = "period" # period From cf6ded03288fc8197de177ce163f29d7652f52e7 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Fri, 29 Aug 2025 21:01:51 +1000 Subject: [PATCH 061/163] fix: replaced the string that using Class IdButtons in explorer.py, natural_ventilation.py, psy-chart.py, summary.py, sun.py, wind.py, t-rh.py and outdoor.py --- pages/explorer.py | 13 +++++++------ pages/lib/charts_sun.py | 8 ++++---- pages/lib/global_column_names.py | 1 + pages/lib/global_id_buttons.py | 2 ++ pages/natural_ventilation.py | 5 +++-- pages/outdoor.py | 7 ++++--- pages/psy-chart.py | 3 ++- pages/summary.py | 7 ++++--- pages/sun.py | 12 +++++++----- pages/t_rh.py | 9 +++++---- pages/wind.py | 7 ++++--- 11 files changed, 43 insertions(+), 31 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index 8573edb3..2910157d 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -14,6 +14,7 @@ ) 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_scheme import ( fig_config, dropdown_names, @@ -84,7 +85,7 @@ def section_one(): html.Div( children=title_with_link( text="Yearly chart", - id_button="explore-yearly-chart-label", + id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL, doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), @@ -95,7 +96,7 @@ def section_one(): html.Div( children=title_with_link( text="Daily chart", - id_button="explore-daily-chart-label", + id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL, doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), @@ -106,7 +107,7 @@ def section_one(): html.Div( children=title_with_link( text="Heatmap chart", - id_button="explore-heatmap-chart-label", + id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL, doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), @@ -118,7 +119,7 @@ def section_one(): children=title_with_tooltip( text="Descriptive statistics", tooltip_text="count, mean, std, min, max, and percentiles", - id_button="table-explore", + id_button=IdButtons.TABLE_EXPLORE, ), ), html.Div( @@ -215,7 +216,7 @@ def section_two_inputs(): children=title_with_tooltip( text="Customizable heatmap", tooltip_text=None, - id_button="custom-heatmap-chart-label", + id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL, ), ), html.Div( @@ -599,7 +600,7 @@ def section_three(): children=title_with_tooltip( text="More charts", tooltip_text=None, - id_button="more-charts-label", + id_button=IdButtons.MORE_CHARTS_LABEL, ), ), section_three_inputs(), diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 17028014..6d979956 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -248,7 +248,7 @@ def polar_graph(df, meta, global_local, var, si_ip): # draw equinox and sostices for date in pd.to_datetime(["2019-03-21", "2019-06-21", "2019-12-21"]): - times = pd.date_range(date, date + pd.Timedelta("24h"), freq="5min", tz=tz) + times = pd.date_range(date, date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), freq=ColNames.FIVE_MINUTE, tz=tz) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] @@ -272,7 +272,7 @@ def polar_graph(df, meta, global_local, var, si_ip): # draw sunpath on the 21st of each other month for date in pd.to_datetime(["2019-01-21", "2019-02-21", "2019-4-21", "2019-5-21"]): - times = pd.date_range(date, date + pd.Timedelta("24h"), freq="5min", tz=tz) + times = pd.date_range(date, date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), freq=ColNames.FIVE_MINUTE, tz=tz) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] @@ -427,7 +427,7 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): # draw equinox and sostices for date in pd.to_datetime(["2019-03-21", "2019-06-21", "2019-12-21"]): - times = pd.date_range(date, date + pd.Timedelta("24h"), freq="5min", tz=tz) + times = pd.date_range(date, date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), freq=ColNames.FIVE_MINUTE, tz=tz) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) @@ -451,7 +451,7 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): # draw sunpath on the 21st of each other month for date in pd.to_datetime(["2019-01-21", "2019-02-21", "2019-4-21", "2019-5-21"]): - times = pd.date_range(date, date + pd.Timedelta("24h"), freq="5min", tz=tz) + times = pd.date_range(date, date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), freq=ColNames.FIVE_MINUTE, tz=tz) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index 589e0483..c498ea76 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -99,6 +99,7 @@ class ColNames: RANGE = "range" UNIT = "unit" TWENTY_FOUR_HOUR = "24h" + FIVE_MINUTE = "5min" TIMES = "times" PATH = "path" diff --git a/pages/lib/global_id_buttons.py b/pages/lib/global_id_buttons.py index 89807520..eb5546c8 100644 --- a/pages/lib/global_id_buttons.py +++ b/pages/lib/global_id_buttons.py @@ -7,6 +7,8 @@ class IdButtons: DAILY_ROSE_CHART = "daily-rose-chart" CLIMATE_PROFILES_CHART = "climate-profiles-chart" PSYCHROMETRIC_CHART_CHART = "Psychrometric-Chart-chart" + CUSTOM_ROSE_CHART = "custom-rose-chart" + HDD_CDD_CHART = "hdd-cdd-chart" MORE_CHARTS_LABEL = "more-charts-label" DOWNLOAD_BUTTON_LABEL = "download-button-label" diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 073f4250..4e89455d 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -20,6 +20,7 @@ from pages.lib.template_graphs import filter_df_by_month_and_hour from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds +from pages.lib.global_id_buttons import IdButtons from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -67,7 +68,7 @@ def update_layout(si_ip): html.Div( children=title_with_link( text="Natural Ventilation Potential", - id_button="natural-ventilation-label", + id_button=IdButtons.NATURAL_VENTILATION_LABEL, doc_link=DocLinks.NATURAL_VENTILATION, ), ), @@ -102,7 +103,7 @@ def update_layout(si_ip): "If normalized is enabled it calculates the % " "time otherwise it calculates the total number of hours" ), - id_button="nv_normalize", + id_button=IdButtons.NV_NORMALIZE, ), ), ], diff --git a/pages/outdoor.py b/pages/outdoor.py index 04380c79..fd5d3de6 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -8,6 +8,7 @@ from config import PageUrls, DocLinks, PageInfo 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_scheme import ( outdoor_dropdown_names, ) @@ -148,7 +149,7 @@ def outdoor_comfort_chart(): html.Div( children=title_with_link( text="UTCI heatmap chart", - id_button="utci-charts-label", + id_button=IdButtons.UTCI_CHARTS_LABEL, doc_link=DocLinks.UTCI_CHART, ) ), @@ -159,7 +160,7 @@ def outdoor_comfort_chart(): html.Div( children=title_with_link( text="UTCI thermal stress chart", - id_button="utci-charts-label", + id_button=IdButtons.UTCI_CHARTS_LABEL, doc_link=DocLinks.UTCI_CHART, ) ), @@ -190,7 +191,7 @@ def outdoor_comfort_chart(): "If normalized is enabled it calculates the % " "time otherwise it calculates the total number of hours" ), - id_button="outdoor-comfort-normalize", + id_button=IdButtons.OUTDOOR_COMFORT_NORMALIZE, ), ), ], diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 54e51ee6..35978f34 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -14,6 +14,7 @@ from config import PageUrls, DocLinks, PageInfo, UnitSystem 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_scheme import ( container_row_center_full, container_col_center_one_of_three, @@ -219,7 +220,7 @@ def layout(): html.Div( children=title_with_link( text="Psychrometric Chart", - id_button="Psychrometric-Chart-chart", + id_button=IdButtons.PSYCHROMETRIC_CHART_CHART, doc_link=DocLinks.PSYCHROMETRIC_CHART, ), ), diff --git a/pages/summary.py b/pages/summary.py index 6c2facac..83609288 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -13,6 +13,7 @@ from pages.lib.template_graphs import violin from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds +from pages.lib.global_id_buttons import IdButtons from pages.lib.utils import ( generate_chart_name, generate_units, @@ -73,7 +74,7 @@ def update_layout(si_ip): html.Div( children=title_with_tooltip( text="Download", - id_button="download-button-label", + id_button=IdButtons.DOWNLOAD_BUTTON_LABEL, tooltip_text="Use the following buttons to download either the Clima sourcefile or the EPW file", ), ), @@ -110,7 +111,7 @@ def update_layout(si_ip): html.Div( children=title_with_link( text="Heating and Cooling Degree Days", - id_button="hdd-cdd-chart", + id_button=IdButtons.HDD_CDD_CHART, doc_link=DocLinks.DEGREE_DAYS, ), ), @@ -171,7 +172,7 @@ def update_layout(si_ip): html.Div( children=title_with_link( text="Climate Profiles", - id_button="climate-profiles-chart", + id_button=IdButtons.CLIMATE_PROFILES_CHART, doc_link=DocLinks.CLIMATE_PROFILES, ), ), diff --git a/pages/sun.py b/pages/sun.py index 9d6a49bc..0fcb4d51 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -7,6 +7,8 @@ from dash import html, dcc from dash_extensions.enrich import Output, Input, State, callback +from pages.lib.global_column_names import ColNames +from pages.lib.global_id_buttons import IdButtons from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.charts_sun import ( monthly_solar, @@ -58,7 +60,7 @@ def sun_path(): html.Div( children=title_with_link( text="Sun path chart", - id_button="sun-path-chart-label", + id_button=IdButtons.SUN_PATH_CHART_LABEL, doc_link=DocLinks.SUN_PATH_DIAGRAM, ), ), @@ -117,7 +119,7 @@ def explore_daily_heatmap(): html.Div( children=title_with_link( text="Daily charts", - id_button="daily-chart-label", + id_button=IdButtons.DAILY_CHART_LABEL, doc_link=DocLinks.CUSTOM_HEATMAP, ), ), @@ -177,7 +179,7 @@ def update_static_section(si_ip): html.Div( children=title_with_link( text="Global and Diffuse Horizontal Solar Radiation (" + hor_unit + ")", - id_button="monthly-chart-label", + id_button=IdButtons.MONTHLY_CHART_LABEL, doc_link=DocLinks.SOLAR_RADIATION, ), ), @@ -188,7 +190,7 @@ def update_static_section(si_ip): html.Div( children=title_with_link( text="Cloud coverage", - id_button="cloud-chart-label", + id_button=IdButtons.CLOUD_CHART_LABEL, doc_link=DocLinks.CLOUD_COVER, ), ), @@ -221,7 +223,7 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): monthly = monthly.update_layout(margin=tight_margins) # Cloud Cover - cover = barchart(df, "tot_sky_cover", [False], [False, "", 3, 7], True, si_ip) + cover = barchart(df, ColNames.TOT_SKY_COVER, [False], [False, "", 3, 7], True, si_ip) cover = cover.update_layout( margin=tight_margins, title="", diff --git a/pages/t_rh.py b/pages/t_rh.py index 92564b67..3d4a8caa 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -5,6 +5,7 @@ from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds +from pages.lib.global_id_buttons import IdButtons from pages.lib.utils import ( generate_chart_name, generate_units, @@ -51,7 +52,7 @@ def layout(): html.Div( children=title_with_link( text="Yearly Chart", - id_button="yearly-chart-label", + id_button=IdButtons.YEARLY_CHART_LABEL, doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), @@ -62,7 +63,7 @@ def layout(): html.Div( children=title_with_link( text="Daily chart", - id_button="daily-chart-label", + id_button=IdButtons.DAILY_CHART_LABEL, doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), @@ -73,7 +74,7 @@ def layout(): html.Div( children=title_with_link( text="Heatmap chart", - id_button="heatmap-chart-label", + id_button=IdButtons.HEATMAP_CHART_LABEL, doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), ), @@ -85,7 +86,7 @@ def layout(): children=title_with_tooltip( text="Descriptive statistics", tooltip_text="count, mean, std, min, max, and percentiles", - id_button="table-tmp-rh", + id_button=IdButtons.TABLE_TMP_RH, ), ), html.Div( diff --git a/pages/wind.py b/pages/wind.py index 711f32b5..2a691831 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -7,6 +7,7 @@ from pages.lib.global_scheme import month_lst, container_row_center_full 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 from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -166,7 +167,7 @@ def daily_wind_rose(): html.Div( children=title_with_link( text="Daily Wind Rose", - id_button="daily-rose-chart", + id_button=IdButtons.DAILY_ROSE_CHART, doc_link=DocLinks.WIND_ROSE, ), ), @@ -242,7 +243,7 @@ def custom_wind_rose(): children=title_with_tooltip( text="Customizable Wind Rose", tooltip_text=None, - id_button="custom-rose-chart", + id_button=IdButtons.CUSTOM_ROSE_CHART, ), ), html.Div( @@ -345,7 +346,7 @@ def layout(): html.Div( children=title_with_link( text="Annual Wind Rose", - id_button="wind-rose-label", + id_button=IdButtons.WIND_ROSE_LABEL, doc_link=DocLinks.WIND_ROSE, ), ), From 01d2a8596c6d07cd99dcc6eee7e38919920b2db3 Mon Sep 17 00:00:00 2001 From: Tianchi Liu Date: Fri, 29 Aug 2025 22:39:40 +1000 Subject: [PATCH 062/163] Fix: Updated ids of files global_element_ids.py,layout.py,main.py,select.py --- main.py | 3 ++- pages/lib/global_element_ids.py | 26 +++++++++++++++++++-- pages/lib/layout.py | 41 +++++++++++++++++---------------- pages/select.py | 8 +++---- 4 files changed, 51 insertions(+), 27 deletions(-) diff --git a/main.py b/main.py index d6fccbfa..d9ceebaf 100644 --- a/main.py +++ b/main.py @@ -5,6 +5,7 @@ from app import app from pages.lib.layout import banner, footer, build_tabs from config import AppConfig +from pages.lib.global_element_ids import ElementIds server = app.server @@ -22,7 +23,7 @@ # callback for survey alert (dbc.Toast) -@callback(Output("alert-auto", "is_open"), Input("interval-component", "n_intervals")) +@callback(Output(ElementIds.ID_MAIN_ALERT_AUTO, "is_open"), Input(ElementIds.ID_MAIN_INTERVAL_COMPONENT, "n_intervals")) def display_alert(n): return n == 1 diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py index 041d5ae7..c67dea33 100644 --- a/pages/lib/global_element_ids.py +++ b/pages/lib/global_element_ids.py @@ -167,12 +167,12 @@ class ElementIds(str, Enum): MODAL = "modal" ALERT = "alert" ID_SELECT_META_STORE = "meta-store" - LINES_STORE = "lines-store" ID_SELECT_URL_STORE = "url-store" ID_SELECT_DF_STORE = "df-store" ID_SELECT_SI_IP_UNIT_STORE = "si-ip-unit-store" ID_SELECT_SI_IP_RADIO_INPUT = "si-ip-radio-input" - BANNER_SUBTITLE = "banner-subtitle" + ID_SELECT_BANNER_SUBTITLE = "banner-subtitle" + ID_SELECT_LINES_STORE = "lines-store" TAB_TWO_CONTAINER = "tab-two-container" ID_SUMMARY_SI_IP_RADIO_INPUT = "si-ip-radio-input" TAB2_SCE1_CONTAINER = "tab2-sec1-container" @@ -201,3 +201,25 @@ 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" + ID_LAYOUT_SI_IP_RADIO_INPUT = "si-ip-radio-input" + STORE = "store" + ID_LAYOUT_DF_STORE = "df-store" + ID_LAYOUT_META_STORE = "meta-store" + 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" \ No newline at end of file diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 2ec78075..795f4044 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -5,12 +5,13 @@ from dash_iconify import DashIconify from pages.lib.global_column_names import ColNames from config import DocLinks, UnitSystem +from pages.lib.global_element_ids import ElementIds def alert(): """Alert for survey.""" return html.Div( - id="alert-container", + id=ElementIds.ALERT_CONTAINER, children=[ dbc.Toast( [ @@ -23,7 +24,7 @@ def alert(): ), "! ☀️", ], - id="alert-auto", + id=ElementIds.ID_LAYOUT_ALERT_AUTO, header="CBE Clima User Survey", icon="info", is_open=False, @@ -31,7 +32,7 @@ def alert(): className="survey-alert", style={"position": "fixed", "top": 25, "right": 10, "width": 400}, ), - dcc.Interval(id="interval-component", interval=12 * 1000, n_intervals=0), + dcc.Interval(id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, interval=12 * 1000, n_intervals=0), ], ) @@ -41,7 +42,7 @@ def footer(): return dbc.Row( align="center", justify="between", - id="footer-container", + id=ElementIds.FOOTER_CONTAINER, children=[ dbc.Col( children=[ @@ -134,7 +135,7 @@ def footer(): def banner(): """Build the banner at the top of the page.""" return html.Div( - id="banner", + id=ElementIds.BANNER, children=[ dmc.Group( position="apart", @@ -159,12 +160,12 @@ def banner(): dmc.Title( "CBE Clima Tool", order=1, - id="banner-title", + id=ElementIds.BANNER_TITLE, style={"fontSize": "2rem"}, ), dmc.Text( "Current Location:", - id="banner-subtitle", + id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE, size="sm", ), ], @@ -186,7 +187,7 @@ def banner(): style={"textDecoration": "none"}, ), dmc.SegmentedControl( - id="global-local-radio-input", + id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT, value="local", radius="md", data=[ @@ -195,7 +196,7 @@ def banner(): ], ), dmc.SegmentedControl( - id="si-ip-radio-input", + id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT, value=UnitSystem.SI, radius="md", data=[ @@ -219,23 +220,23 @@ def banner(): def store(): return html.Div( - id="store", + id=ElementIds.STORE, children=[ - dcc.Store(id="df-store", storage_type="session"), - dcc.Store(id="meta-store", storage_type="session"), - dcc.Store(id="url-store", storage_type="session"), - dcc.Store(id="si-ip-unit-store", storage_type="session"), - dcc.Store(id="lines-store", storage_type="session"), + dcc.Store(id=ElementIds.ID_LAYOUT_DF_STORE, storage_type="session"), + dcc.Store(id=ElementIds.ID_LAYOUT_META_STORE, storage_type="session"), + 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"), ], ) def build_tabs(): return html.Div( - id="tabs-container", + id=ElementIds.TABS_CONTAINER, children=[ html.Div( - id="tabs-parent", + id=ElementIds.TABS_PARENT, className="custom-tabs", children=[ dbc.Nav( @@ -254,7 +255,7 @@ def build_tabs(): for page in dash.page_registry.values() if page[ColNames.NAME] not in ["404", "changelog"] ], - id="tabs", + id=ElementIds.TABS, class_name="tab-container", pills=True, justified=True, @@ -262,11 +263,11 @@ def build_tabs(): ], ), html.Div( - id="store-container", + id=ElementIds.STORE_CONTAINER, children=[ store(), html.Div( - id="tabs-content", + id=ElementIds.TABS_CONTENT, children=[ alert(), # alert can be removed after survey is done dash.page_container, diff --git a/pages/select.py b/pages/select.py index 65805cc4..5020ffc8 100644 --- a/pages/select.py +++ b/pages/select.py @@ -113,7 +113,7 @@ def alert(): @callback( [ Output(ElementIds.ID_SELECT_META_STORE, "data"), - Output(ElementIds.LINES_STORE, "data"), + Output(ElementIds.ID_SELECT_LINES_STORE, "data"), Output(ElementIds.ALERT, "is_open"), Output(ElementIds.ALERT, "children"), Output(ElementIds.ALERT, "color"), @@ -211,10 +211,10 @@ def submitted_data( Output(ElementIds.ID_SELECT_SI_IP_UNIT_STORE, "data"), ], [ - Input(ElementIds.LINES_STORE, "modified_timestamp"), + Input(ElementIds.ID_SELECT_LINES_STORE, "modified_timestamp"), Input(ElementIds.ID_SELECT_SI_IP_RADIO_INPUT, "value"), ], - [State(ElementIds.ID_SELECT_URL_STORE, "data"), State("lines-store", "data")], + [State(ElementIds.ID_SELECT_URL_STORE, "data"), State(ElementIds.ID_SELECT_LINES_STORE, "data")], ) def switch_si_ip(_, si_ip_input, url_store, lines): if lines is not None: @@ -241,7 +241,7 @@ def switch_si_ip(_, si_ip_input, url_store, lines): Output("/explorer", "disabled"), Output("/outdoor", "disabled"), Output("/natural-ventilation", "disabled"), - Output(ElementIds.BANNER_SUBTITLE, "children"), + Output(ElementIds.ID_SELECT_BANNER_SUBTITLE, "children"), ], [ Input(ElementIds.ID_SELECT_META_STORE, "data"), From d5eda1ecc060744e9ebd4dd57df7bcc10c848d0c Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sat, 30 Aug 2025 14:45:55 +1000 Subject: [PATCH 063/163] fix: fixed code sanitisation - add global tab names python file to store tab name value - replaced the tab_name via using global_tab_names.py - removed unused import: `enum.Enum` - formatted the code style via running ruff and pre-commit --- main.py | 5 +++- pages/explorer.py | 31 +++++++++++++++------ pages/lib/charts_sun.py | 28 ++++++++++++++++--- pages/lib/global_column_names.py | 3 -- pages/lib/global_element_ids.py | 4 +-- pages/lib/global_id_buttons.py | 2 +- pages/lib/global_tab_names.py | 47 ++++++++++++++++++++++++++++++++ pages/lib/layout.py | 6 +++- pages/natural_ventilation.py | 5 ++-- pages/outdoor.py | 9 ++++-- pages/psy-chart.py | 3 +- pages/select.py | 8 ++++-- pages/summary.py | 13 +++++---- pages/sun.py | 21 +++++++++----- pages/t_rh.py | 19 +++++++++---- pages/wind.py | 25 +++++++++-------- 16 files changed, 171 insertions(+), 58 deletions(-) create mode 100644 pages/lib/global_tab_names.py diff --git a/main.py b/main.py index d9ceebaf..e5c23662 100644 --- a/main.py +++ b/main.py @@ -23,7 +23,10 @@ # callback for survey alert (dbc.Toast) -@callback(Output(ElementIds.ID_MAIN_ALERT_AUTO, "is_open"), Input(ElementIds.ID_MAIN_INTERVAL_COMPONENT, "n_intervals")) +@callback( + Output(ElementIds.ID_MAIN_ALERT_AUTO, "is_open"), + Input(ElementIds.ID_MAIN_INTERVAL_COMPONENT, "n_intervals"), +) def display_alert(n): return n == 1 diff --git a/pages/explorer.py b/pages/explorer.py index 2910157d..dde7df5c 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -15,6 +15,7 @@ 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 ( fig_config, dropdown_names, @@ -652,7 +653,9 @@ def update_tab_yearly(_, var, global_local, df, meta, si_ip): custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("yearly_explore", meta, custom_inputs, units), + config=generate_chart_name( + TabNames.YEARLY_EXPLORE, meta, custom_inputs, units + ), figure=yearly_profile(df, var, global_local, si_ip), ) @@ -676,7 +679,9 @@ def update_tab_daily(_, var, global_local, df, meta, si_ip): units = generate_units(si_ip) return ( dcc.Graph( - config=generate_chart_name("daily_explore", meta, custom_inputs, units), + config=generate_chart_name( + TabNames.DAILY_EXPLORE, meta, custom_inputs, units + ), figure=daily_profile(df, var, global_local, si_ip), ), ) @@ -701,7 +706,9 @@ def update_tab_heatmap(_, var, global_local, df, meta, si_ip): units = generate_units(si_ip) return ( dcc.Graph( - config=generate_chart_name("heatmap_explore", meta, custom_inputs, units), + config=generate_chart_name( + TabNames.HEATMAP_EXPLORE, meta, custom_inputs, units + ), figure=heatmap(df, var, global_local, si_ip), ), ) @@ -800,7 +807,9 @@ def update_heatmap( units = generate_units(si_ip) return ( dcc.Graph( - config=generate_chart_name("heatmap", meta, custom_inputs, units), + config=generate_chart_name( + TabNames.HEATMAP, meta, custom_inputs, units + ), figure=heat_map, ), {}, @@ -815,7 +824,7 @@ def update_heatmap( return ( dcc.Graph( - config=generate_chart_name("heatmap", meta, custom_inputs, units), + config=generate_chart_name(TabNames.HEATMAP, meta, custom_inputs, units), figure=heat_map, ), no_display, @@ -900,7 +909,9 @@ def update_more_charts( color="danger", style={"text-align": "center", "marginTop": "2rem"}, ), dcc.Graph( - config=generate_chart_name("scatter", meta, custom_inputs, units), + config=generate_chart_name( + TabNames.SCATTER, meta, custom_inputs, units + ), figure=two, ) else: @@ -908,10 +919,14 @@ def update_more_charts( custom_inputs_two = f"{var_x}-{var_y}" units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("scatter", meta, custom_inputs_three, units), + config=generate_chart_name( + TabNames.SCATTER, meta, custom_inputs_three, units + ), figure=three, ), dcc.Graph( - config=generate_chart_name("scatter", meta, custom_inputs_two, units), + config=generate_chart_name( + TabNames.SCATTER, meta, custom_inputs_two, units + ), figure=two, ) diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 6d979956..c6449ddc 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -248,7 +248,12 @@ def polar_graph(df, meta, global_local, var, si_ip): # draw equinox and sostices for date in pd.to_datetime(["2019-03-21", "2019-06-21", "2019-12-21"]): - times = pd.date_range(date, date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), freq=ColNames.FIVE_MINUTE, tz=tz) + times = pd.date_range( + date, + date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), + freq=ColNames.FIVE_MINUTE, + tz=tz, + ) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] @@ -272,7 +277,12 @@ def polar_graph(df, meta, global_local, var, si_ip): # draw sunpath on the 21st of each other month for date in pd.to_datetime(["2019-01-21", "2019-02-21", "2019-4-21", "2019-5-21"]): - times = pd.date_range(date, date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), freq=ColNames.FIVE_MINUTE, tz=tz) + times = pd.date_range( + date, + date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), + freq=ColNames.FIVE_MINUTE, + tz=tz, + ) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] @@ -427,7 +437,12 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): # draw equinox and sostices for date in pd.to_datetime(["2019-03-21", "2019-06-21", "2019-12-21"]): - times = pd.date_range(date, date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), freq=ColNames.FIVE_MINUTE, tz=tz) + times = pd.date_range( + date, + date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), + freq=ColNames.FIVE_MINUTE, + tz=tz, + ) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) @@ -451,7 +466,12 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): # draw sunpath on the 21st of each other month for date in pd.to_datetime(["2019-01-21", "2019-02-21", "2019-4-21", "2019-5-21"]): - times = pd.date_range(date, date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), freq=ColNames.FIVE_MINUTE, tz=tz) + times = pd.date_range( + date, + date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), + freq=ColNames.FIVE_MINUTE, + tz=tz, + ) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index c498ea76..c2ef2135 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -1,6 +1,3 @@ -from enum import Enum - - class ColNames: # ==================== Time related column ==================== YEAR = "year" # year diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py index c67dea33..644dcd3e 100644 --- a/pages/lib/global_element_ids.py +++ b/pages/lib/global_element_ids.py @@ -220,6 +220,6 @@ class ElementIds(str, Enum): ID_LAYOUT_LINES_STORE = "lines-store" TABS_CONTAINER = "tabs-container" TABS_PARENT = "tabs-parent" - TABS ="tabs" + TABS = "tabs" STORE_CONTAINER = "store-container" - TABS_CONTENT = "tabs-content" \ No newline at end of file + TABS_CONTENT = "tabs-content" diff --git a/pages/lib/global_id_buttons.py b/pages/lib/global_id_buttons.py index eb5546c8..79e0d918 100644 --- a/pages/lib/global_id_buttons.py +++ b/pages/lib/global_id_buttons.py @@ -25,4 +25,4 @@ class IdButtons: CLOUD_CHART_LABEL = "cloud-chart-label" YEARLY_CHART_LABEL = "yearly-chart-label" HEATMAP_CHART_LABEL = "heatmap-chart-label" - WIND_ROSE_LABEL = "wind-rose-label" \ No newline at end of file + WIND_ROSE_LABEL = "wind-rose-label" diff --git a/pages/lib/global_tab_names.py b/pages/lib/global_tab_names.py new file mode 100644 index 00000000..0e7af9ce --- /dev/null +++ b/pages/lib/global_tab_names.py @@ -0,0 +1,47 @@ +class TabNames: + DAILY = "daily" + DAILY_EXPLORE = "daily_explore" + YEARLY_EXPLORE = "yearly_explore" + + PSY = "psy" + MAP = "map" + HEATMAP = "heatmap" + HEATMAP_CATEGORY = "heatmap_category" + HEATMAP_EXPLORE = "heatmap_explore" + + SCATTER = "scatter" + SUMMARY = "summary" + BARCHART = "barchart" + + HDD_CDD = "hdd_cdd" + WIND_SPEED = "WindSpeed" + CLOUD_COVER = "cloud_cover" + EPW_LOCATION_SELECT = "epw_location_select" + RELATIVE_HUMIDITY = "RelativeHumidity" + RELATIVE_HUMIDITY_HEATMAP = "RelativeHumidity_heatmap" + DRY_BULB_TEMPERATURE = "DryBulbTemperature" + DRY_BULB_TEMPERATURE_HEATMAP = "DryBulbTemperature_heatmap" + + GLOBAL_HORIZONTAL_RADIATION = "GlobalHorizontalRadiation" + GLOBAL_AND_DIFFUSE_HORIZONTAL_SOLAR_RADIATION = ( + "global_and_diffuse_horizontal_solar_radiation" + ) + + SPHERICAL_SUNPATH = "spherical_sunpath" + CARTESIAN_SUNPATH = "cartesian_sunpath" + + DRY_BULB_TEMPERATURE_DAILY = "DryBulbTemperature_daily" + RELATIVE_HUMIDITY_DAILY = "RelativeHumidity_daily" + DRY_BULB_TEMPERATURE_YEARLY = "DryBulbTemperature_yearly" + RELATIVE_HUMIDITY_YEARLY = "RelativeHumidity_yearly" + + ANNUAL_WIND_ROSE = "annual_wind_rose" + WIND_DIRECTION = "wind_direction" + CUSTOM_WIND_ROSE = "custom_wind_rose" + SPRING_WIND_ROSE = "spring_wind_rose" + SUMMER_WIND_ROSE = "summer_wind_rose" + FALL_WIND_ROSE = "fall_wind_rose" + WINTER_WIND_ROSE = "winter_wind_rose" + MORNING_WIND_ROSE = "morning_wind_rose" + NOON_WIND_ROSE = "noon_wind_rose" + NIGHT_WIND_ROSE = "night_wind_rose" diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 795f4044..f6cff013 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -32,7 +32,11 @@ def alert(): className="survey-alert", style={"position": "fixed", "top": 25, "right": 10, "width": 400}, ), - dcc.Interval(id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, interval=12 * 1000, n_intervals=0), + dcc.Interval( + id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, + interval=12 * 1000, + n_intervals=0, + ), ], ) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 4e89455d..287512ee 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -21,6 +21,7 @@ from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds from pages.lib.global_id_buttons import IdButtons +from pages.lib.global_tab_names import TabNames from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -446,7 +447,7 @@ def nv_heatmap( ) units = generate_units_degree(si_ip) return dcc.Graph( - config=generate_chart_name("heatmap", meta, custom_inputs, units), + config=generate_chart_name(TabNames.HEATMAP, meta, custom_inputs, units), figure=fig, ) @@ -614,7 +615,7 @@ def nv_bar_chart( ) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("barchart", meta, custom_inputs, units), + config=generate_chart_name(TabNames.BARCHART, meta, custom_inputs, units), figure=fig, ) diff --git a/pages/outdoor.py b/pages/outdoor.py index fd5d3de6..0e4885ad 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -9,6 +9,7 @@ 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 ( outdoor_dropdown_names, ) @@ -295,7 +296,7 @@ def update_tab_utci_value( custom_inputs = f"{var}" units = generate_units_degree(si_ip) return dcc.Graph( - config=generate_chart_name("heatmap", meta, custom_inputs, units), + config=generate_chart_name(TabNames.HEATMAP, meta, custom_inputs, units), figure=heatmap_with_filter( df, var, @@ -393,7 +394,9 @@ def update_tab_utci_category( custom_inputs = f"{var}" units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("heatmap_category", meta, custom_inputs, units), + config=generate_chart_name( + TabNames.HEATMAP_CATEGORY, meta, custom_inputs, units + ), figure=utci_stress_cat, ) @@ -432,6 +435,6 @@ def update_tab_utci_summary_chart( custom_inputs = f"{var}" units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("summary", meta, custom_inputs, units), + config=generate_chart_name(TabNames.SUMMARY, meta, custom_inputs, units), figure=utci_summary_chart, ) diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 35978f34..fc49e8f2 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -15,6 +15,7 @@ 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, @@ -514,5 +515,5 @@ def update_psych_chart( ) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("psy", meta, custom_inputs, units), figure=fig + config=generate_chart_name(TabNames.PSY, meta, custom_inputs, units), figure=fig ) diff --git a/pages/select.py b/pages/select.py index 5020ffc8..86c22d87 100644 --- a/pages/select.py +++ b/pages/select.py @@ -15,6 +15,7 @@ from pages.lib.extract_df import create_df, get_data, get_location_info from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds +from pages.lib.global_tab_names import TabNames from pages.lib.global_scheme import mapping_dictionary from config import PageUrls, PageInfo, UnitSystem from pages.lib.utils import generate_chart_name @@ -214,7 +215,10 @@ def submitted_data( Input(ElementIds.ID_SELECT_LINES_STORE, "modified_timestamp"), Input(ElementIds.ID_SELECT_SI_IP_RADIO_INPUT, "value"), ], - [State(ElementIds.ID_SELECT_URL_STORE, "data"), State(ElementIds.ID_SELECT_LINES_STORE, "data")], + [ + State(ElementIds.ID_SELECT_URL_STORE, "data"), + State(ElementIds.ID_SELECT_LINES_STORE, "data"), + ], ) def switch_si_ip(_, si_ip_input, url_store, lines): if lines is not None: @@ -374,6 +378,6 @@ def plot_location_epw_files(pathname): dcc.Graph( id=ElementIds.TAB_ONE_MAP, figure=fig, - config=generate_chart_name("epw_location_select"), + config=generate_chart_name(TabNames.EPW_LOCATION_SELECT), ), ) diff --git a/pages/summary.py b/pages/summary.py index 83609288..3177e70e 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -14,6 +14,7 @@ from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds from pages.lib.global_id_buttons import IdButtons +from pages.lib.global_tab_names import TabNames from pages.lib.utils import ( generate_chart_name, generate_units, @@ -208,7 +209,7 @@ 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, - config=generate_chart_name("map", meta), + config=generate_chart_name(TabNames.MAP, meta), figure=world_map(meta), ) @@ -417,7 +418,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ chart = dcc.Graph( id=ElementIds.DEGREE_DAYS_CHART, - config=generate_chart_name("hdd_cdd", meta, custom_inputs, units), + config=generate_chart_name(TabNames.HDD_CDD, meta, custom_inputs, units), figure=fig, ) @@ -441,7 +442,7 @@ def update_violin_tdb(ts, global_local, df, meta, si_ip): return dcc.Graph( id=ElementIds.TDB_PROFILE_GRAPH, className="violin-container", - config=generate_chart_name("DryBulbTemperature", meta, units), + config=generate_chart_name(TabNames.DRY_BULB_TEMPERATURE, meta, units), figure=violin(df, ColNames.DBT, global_local, si_ip), ) @@ -464,7 +465,7 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): return dcc.Graph( id=ElementIds.WIND_PROFILE_GRAPH, className="violin-container", - config=generate_chart_name("WindSpeed", meta, units), + config=generate_chart_name(TabNames.WIND_SPEED, meta, units), figure=violin(df, ColNames.WIND_SPEED, global_local, si_ip), ) @@ -487,7 +488,7 @@ def update_tab_rh(ts, global_local, df, meta, si_ip): return dcc.Graph( id=ElementIds.RH_PROFILE_GRAPH, className="violin-container", - config=generate_chart_name("RelativeHumidity", meta, units), + config=generate_chart_name(TabNames.RELATIVE_HUMIDITY, meta, units), figure=violin(df, ColNames.RH, global_local, si_ip), ) @@ -510,7 +511,7 @@ def update_tab_gh_rad(ts, global_local, df, meta, si_ip): return dcc.Graph( id=ElementIds.GH_RAD_PROFILE_GRAPH, className="violin-container", - config=generate_chart_name("GlobalHorizontalRadiation", meta, units), + 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 0fcb4d51..919655f4 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -9,6 +9,7 @@ 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 config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.charts_sun import ( monthly_solar, @@ -223,7 +224,9 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): monthly = monthly.update_layout(margin=tight_margins) # Cloud Cover - cover = barchart(df, ColNames.TOT_SKY_COVER, [False], [False, "", 3, 7], True, si_ip) + cover = barchart( + df, ColNames.TOT_SKY_COVER, [False], [False, "", 3, 7], True, si_ip + ) cover = cover.update_layout( margin=tight_margins, title="", @@ -235,11 +238,11 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): units = generate_units(si_ip) return dcc.Graph( config=generate_chart_name( - "Global_and_Diffuse_Horizontal_Solar_Radiation", meta, units + TabNames.GLOBAL_AND_DIFFUSE_HORIZONTAL_SOLAR_RADIATION, meta, units ), figure=monthly, ), dcc.Graph( - config=generate_chart_name("cloud_cover", meta, units), + config=generate_chart_name(TabNames.CLOUD_COVER, meta, units), figure=cover, ) @@ -264,12 +267,16 @@ 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( - config=generate_chart_name("spherical_sunpath", meta, custom_inputs, units), + config=generate_chart_name( + TabNames.SPHERICAL_SUNPATH, meta, custom_inputs, units + ), figure=polar_graph(df, meta, global_local, var, si_ip), ) else: return dcc.Graph( - config=generate_chart_name("cartesian_sunpath", meta, custom_inputs, units), + config=generate_chart_name( + TabNames.CARTESIAN_SUNPATH, meta, custom_inputs, units + ), figure=custom_cartesian_solar(df, meta, global_local, var, si_ip), ) @@ -292,7 +299,7 @@ def daily(_, var, global_local, df, meta, si_ip): custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("daily", meta, custom_inputs, units), + config=generate_chart_name(TabNames.DAILY, meta, custom_inputs, units), figure=daily_profile(df, var, global_local, si_ip), ) @@ -314,6 +321,6 @@ def update_heatmap(_, var, global_local, df, meta, si_ip): custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("heatmap", meta, custom_inputs, units), + 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 3d4a8caa..6729a31d 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -6,6 +6,7 @@ from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds from pages.lib.global_id_buttons import IdButtons +from pages.lib.global_tab_names import TabNames from pages.lib.utils import ( generate_chart_name, generate_units, @@ -117,7 +118,9 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): dbt_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) units = generate_units_degree(si_ip) return dcc.Graph( - config=generate_chart_name("DryBulbTemperature_yearly", meta, units), + config=generate_chart_name( + TabNames.DRY_BULB_TEMPERATURE_YEARLY, meta, units + ), figure=dbt_yearly, ) else: @@ -125,7 +128,7 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): rh_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("RelativeHumidity_yearly", meta, units), + config=generate_chart_name(TabNames.RELATIVE_HUMIDITY_YEARLY, meta, units), figure=rh_yearly, ) @@ -147,7 +150,9 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): if dd_value == dropdown_names[var_to_plot[0]]: units = generate_units_degree(si_ip) return dcc.Graph( - config=generate_chart_name("DryBulbTemperature_daily", meta, units), + config=generate_chart_name( + TabNames.DRY_BULB_TEMPERATURE_DAILY, meta, units + ), figure=daily_profile( df[ [ @@ -167,7 +172,7 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): else: units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("RelativeHumidity_daily", meta, units), + config=generate_chart_name(TabNames.RELATIVE_HUMIDITY_DAILY, meta, units), figure=daily_profile( df[ [ @@ -204,7 +209,9 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): if dd_value == dropdown_names[var_to_plot[0]]: units = generate_units_degree(si_ip) return dcc.Graph( - config=generate_chart_name("DryBulbTemperature_heatmap", meta, units), + config=generate_chart_name( + TabNames.DRY_BULB_TEMPERATURE_HEATMAP, meta, units + ), figure=heatmap( df[ [ @@ -223,7 +230,7 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): else: units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("RelativeHumidity_heatmap", meta, units), + config=generate_chart_name(TabNames.RELATIVE_HUMIDITY_HEATMAP, meta, units), figure=heatmap( df[ [ diff --git a/pages/wind.py b/pages/wind.py index 2a691831..a4f7b948 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -8,6 +8,7 @@ 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 +from pages.lib.global_tab_names import TabNames from pages.lib.utils import ( title_with_tooltip, generate_chart_name, @@ -387,7 +388,7 @@ def update_annual_wind_rose(_, df, meta, si_ip): annual = wind_rose(df, "", [1, 12], [1, 24], True, si_ip) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("annual_wind_rose", meta, units), + config=generate_chart_name(TabNames.ANNUAL_WIND_ROSE, meta, units), figure=annual, ) @@ -412,7 +413,7 @@ def update_tab_wind_speed(_, global_local, df, meta, si_ip): speed = heatmap(df, ColNames.WIND_SPEED, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name(ColNames.WIND_SPEED, meta, units), + config=generate_chart_name(TabNames.WIND_SPEED, meta, units), figure=speed, ) @@ -436,7 +437,7 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): direction = heatmap(df, ColNames.WIND_DIR, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("wind_direction", meta, units), + config=generate_chart_name(TabNames.WIND_DIRECTION, meta, units), figure=direction, ) @@ -489,7 +490,9 @@ def update_custom_wind_rose( ) units = generate_units(si_ip) return dcc.Graph( - config=generate_chart_name("custom_wind_rose", meta, custom_inputs, units), + config=generate_chart_name( + TabNames.CUSTOM_WIND_ROSE, meta, custom_inputs, units + ), figure=custom, ) @@ -592,19 +595,19 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): units = generate_units(si_ip) return ( dcc.Graph( - config=generate_chart_name("winter_wind_rose", meta, units), + config=generate_chart_name(TabNames.WINTER_WIND_ROSE, meta, units), figure=winter, ), dcc.Graph( - config=generate_chart_name("spring_wind_rose", meta, units), + config=generate_chart_name(TabNames.SPRING_WIND_ROSE, meta, units), figure=spring, ), dcc.Graph( - config=generate_chart_name("summer_wind_rose", meta, units), + config=generate_chart_name(TabNames.SUMMER_WIND_ROSE, meta, units), figure=summer, ), dcc.Graph( - config=generate_chart_name("fall_wind_rose", meta, units), + config=generate_chart_name(TabNames.FALL_WIND_ROSE, meta, units), figure=fall, ), winter_text, @@ -690,15 +693,15 @@ def daily_chart_caption(hour_start, hour_end, count, calm_count): units = generate_units(si_ip) return ( dcc.Graph( - config=generate_chart_name("morning_wind_rose", meta, units), + config=generate_chart_name(TabNames.MORNING_WIND_ROSE, meta, units), figure=morning, ), dcc.Graph( - config=generate_chart_name("noon_wind_rose", meta, units), + config=generate_chart_name(TabNames.NOON_WIND_ROSE, meta, units), figure=noon, ), dcc.Graph( - config=generate_chart_name("night_wind_rose", meta, units), + config=generate_chart_name(TabNames.NIGHT_WIND_ROSE, meta, units), figure=night, ), morning_text, From 965a5379e4f038319691eb115a784d8d1fd8e7a0 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sat, 30 Aug 2025 15:08:18 +1000 Subject: [PATCH 064/163] fix: fixed the id_button aligned with IdButtons.* and aligned var/file_var to constants. --- pages/lib/global_id_buttons.py | 1 + pages/natural_ventilation.py | 4 ++-- pages/wind.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pages/lib/global_id_buttons.py b/pages/lib/global_id_buttons.py index 79e0d918..afd4fb88 100644 --- a/pages/lib/global_id_buttons.py +++ b/pages/lib/global_id_buttons.py @@ -5,6 +5,7 @@ class IdButtons: TABLE_TMP_RH = "table-tmp-rh" DAILY_ROSE_CHART = "daily-rose-chart" + SEASONAL_WIND_ROSE_DOC = "seasonal-wind-rose-doc" CLIMATE_PROFILES_CHART = "climate-profiles-chart" PSYCHROMETRIC_CHART_CHART = "Psychrometric-Chart-chart" CUSTOM_ROSE_CHART = "custom-rose-chart" diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 287512ee..1f5f7ce7 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -500,8 +500,8 @@ def nv_bar_chart( month, hour, invert_month, invert_hour ) - var = "DBT" - filter_var = "DPT" + var = ColNames.DBT + filter_var = ColNames.DPT var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT] diff --git a/pages/wind.py b/pages/wind.py index a4f7b948..25c0bd9f 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -77,7 +77,7 @@ def seasonal_wind_rose(): html.Div( children=title_with_link( text="Seasonal Wind Rose", - id_button=ElementIds.SEASONAL_WIND_ROSE_DOC, + id_button=IdButtons.SEASONAL_WIND_ROSE_DOC, doc_link=DocLinks.WIND_ROSE, ), ), From d5223c8c13d1a64a9c28c5a315fc18ff035c3574 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Mon, 1 Sep 2025 12:53:27 +1000 Subject: [PATCH 065/163] fix(data): Replace string references with ColNames for wind speed Update instances of "wind_speed" to ColNames.WIND_SPEED in multiple files to improve code consistency and maintainability. --- pages/lib/extract_df.py | 4 ++-- pages/lib/global_scheme.py | 6 +++--- pages/lib/template_graphs.py | 2 +- pages/summary.py | 5 +++-- pages/wind.py | 4 ++-- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index cb4153a3..bda5e380 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -132,7 +132,7 @@ def create_df(lst, file_name): "dif_hor_ill", "Zlumi", "wind_dir", - "wind_speed", + ColNames.WIND_SPEED, "tot_sky_cover", "Oskycover", "Vis", @@ -207,7 +207,7 @@ def create_df(lst, file_name): "dif_hor_ill", "Zlumi", "wind_dir", - "wind_speed", + ColNames.WIND_SPEED, "tot_sky_cover", "Oskycover", "Vis", diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index 51d51a0d..0029c946 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -365,7 +365,7 @@ }, "conversion_function": None, }, - "wind_speed": { + ColNames.WIND_SPEED: { "name": "Wind speed", "color": [ "#D3D3D3", @@ -840,7 +840,7 @@ "dif_hor_ill", "Zlumi", "wind_dir", - "wind_speed", + ColNames.WIND_SPEED, "tot_sky_cover", "Oskycover", "Vis", @@ -862,7 +862,7 @@ "dif_hor_ill", "Zlumi", "wind_dir", - "wind_speed", + ColNames.WIND_SPEED, "tot_sky_cover", "Oskycover", "Vis", diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index e16ef192..c57fd22d 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -512,7 +512,7 @@ def wind_rose(df, title, month, hour, labels, si_ip): dir_bins = np.arange(-22.5 / 2, 360 + 22.5, 22.5) dir_labels = (dir_bins[:-1] + dir_bins[1:]) / 2 total_count = df.shape[0] - calm_count = df.query("wind_speed == 0").shape[0] + calm_count = df.query(f"{ColNames.WIND_SPEED} == 0").shape[0] # Create a temporary DataFrame with binned data df_binned = df.assign( diff --git a/pages/summary.py b/pages/summary.py index 3177e70e..de5412d9 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -233,12 +233,13 @@ def update_location_info(ts, df, meta, si_ip): site_elevation = float(meta[ColNames.SITE_ELEVATION]) site_elevation = round(site_elevation, 2) + + elevation = f"Elevation above sea level: {str(site_elevation)} m" 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" - else: - elevation = f"Elevation above sea level: {meta[ColNames.SITE_ELEVATION]} m" + period = "" if meta[ColNames.PERIOD]: start, stop = meta[ColNames.PERIOD].split("-") diff --git a/pages/wind.py b/pages/wind.py index 25c0bd9f..8b744447 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -535,7 +535,7 @@ def update_seasonal_graphs(_, df, meta, si_ip): (df[ColNames.MONTH] <= winter_months[1]) | (df[ColNames.MONTH] >= winter_months[0]) ] - query_calm_wind = "wind_speed == 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] @@ -649,7 +649,7 @@ def update_daily_graphs(_, df, meta, si_ip): night = wind_rose(df, "", months, night_times, True, si_ip) # Text - query_calm_wind = "wind_speed == 0" + query_calm_wind = f"{ColNames.WIND_SPEED} == 0" morning_df = df.loc[ (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1]) From 54d6016c20a5cf808a140ec5aca657fd84c952c0 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Mon, 1 Sep 2025 12:56:33 +1000 Subject: [PATCH 066/163] fix(layout): Correct capitalization of yearly chart label Update the label for the yearly chart to use consistent capitalization for improved readability and consistency in the UI. --- tests/node/cypress/e2e/spec.cy.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index dcc84470..8cb41333 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -35,7 +35,7 @@ describe('Clima', () => { // Temperature and Humidity click_tab('Temperature and Humidity'); - cy.contains('Yearly chart'); + cy.contains('Yearly Chart'); cy.contains('Dry bulb temperature (°C)'); // TODO: simulate mouseover cy.contains('Daily chart'); From 9a6d93bb57878c36035bb2e416c730b55a713f18 Mon Sep 17 00:00:00 2001 From: FengW01 Date: Tue, 2 Sep 2025 16:25:20 +1000 Subject: [PATCH 067/163] Refactor: unify UTCI calculations in extract_df.py and add time_filtering in template_graphs.py to reduce duplication --- pages/lib/extract_df.py | 80 +++++++++++++++++++----------------- pages/lib/template_graphs.py | 27 ++++++------ 2 files changed, 54 insertions(+), 53 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index bda5e380..f3fb8112 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -76,6 +76,44 @@ def get_location_info(lst, file_name): return location_info +# ==== Unified UTCI computation and binning ==== +UTCI_BINS = [-999, -40, -27, -13, 0, 9, 26, 32, 38, 46, 999] +UTCI_LABELS = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] + +def utci_calc(df: pd.DataFrame, t_air_col, t_rad_col, wind_col, rh_col=ColNames.RH): + """Call utci() using values from df columns.""" + return utci(df[t_air_col], df[t_rad_col], df[wind_col], df[rh_col]) + +def add_utci_variants(df: pd.DataFrame) -> pd.DataFrame: + """ + Generate the four UTCI variants: + - noSun_Wind : DBT + DBT + wind_speed_utci + - noSun_noWind : DBT + DBT + wind_speed_utci_0 + - Sun_Wind : DBT + MRT + wind_speed_utci + - Sun_noWind : DBT + MRT + wind_speed_utci_0 + """ + recipes = { + ColNames.UTCI_NO_SUN_WIND: (ColNames.DBT, ColNames.DBT, ColNames.WIND_SPEED_UTCI), + ColNames.UTCI_NO_SUN_NO_WIND: (ColNames.DBT, ColNames.DBT, ColNames.WIND_SPEED_UTCI_0), + ColNames.UTCI_SUN_WIND: (ColNames.DBT, ColNames.MRT, ColNames.WIND_SPEED_UTCI), + ColNames.UTCI_SUN_NO_WIND: (ColNames.DBT, ColNames.MRT, ColNames.WIND_SPEED_UTCI_0), + } + for out_col, (t_air, t_rad, wind) in recipes.items(): + df[out_col] = utci_calc(df, t_air, t_rad, wind) + return df + +def add_utci_categories(df: pd.DataFrame) -> pd.DataFrame: + """Bin the four UTCI columns into categories.""" + mapping = { + ColNames.UTCI_NO_SUN_WIND: ColNames.UTCI_NOSUN_WIND_CATEGORIES, + ColNames.UTCI_NO_SUN_NO_WIND: ColNames.UTCI_NOSUN_NOWIND_CATEGORIES, + ColNames.UTCI_SUN_WIND: ColNames.UTCI_SUN_WIND_CATEGORIES, + ColNames.UTCI_SUN_NO_WIND: ColNames.UTCI_SUN_NOWIND_CATEGORIES, + } + for src_col, dst_col in mapping.items(): + df[dst_col] = pd.cut(x=df[src_col], bins=UTCI_BINS, labels=UTCI_LABELS) + return df + @code_timer def create_df(lst, file_name): @@ -280,45 +318,11 @@ def create_df(lst, file_name): epw_df[ColNames.WIND_SPEED_UTCI_0] = epw_df[ColNames.WIND_SPEED_UTCI].mask( epw_df[ColNames.WIND_SPEED_UTCI] >= 0, 0.5 ) - epw_df[ColNames.UTCI_NO_SUN_WIND] = utci( - epw_df[ColNames.DBT], - epw_df[ColNames.DBT], - epw_df[ColNames.WIND_SPEED_UTCI], - epw_df[ColNames.RH], - ) - epw_df[ColNames.UTCI_NO_SUN_NO_WIND] = utci( - epw_df[ColNames.DBT], - epw_df[ColNames.DBT], - epw_df[ColNames.WIND_SPEED_UTCI_0], - epw_df[ColNames.RH], - ) - epw_df[ColNames.UTCI_SUN_WIND] = utci( - epw_df[ColNames.DBT], - epw_df[ColNames.MRT], - epw_df[ColNames.WIND_SPEED_UTCI], - epw_df[ColNames.RH], - ) - epw_df[ColNames.UTCI_SUN_NO_WIND] = utci( - epw_df[ColNames.DBT], - epw_df[ColNames.MRT], - epw_df[ColNames.WIND_SPEED_UTCI_0], - epw_df[ColNames.RH], - ) + + epw_df = add_utci_variants(epw_df) + + epw_df = add_utci_categories(epw_df) - utci_bins = [-999, -40, -27, -13, 0, 9, 26, 32, 38, 46, 999] - utci_labels = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] - epw_df[ColNames.UTCI_NOSUN_WIND_CATEGORIES] = pd.cut( - x=epw_df[ColNames.UTCI_NO_SUN_WIND], bins=utci_bins, labels=utci_labels - ) - epw_df[ColNames.UTCI_NOSUN_NOWIND_CATEGORIES] = pd.cut( - x=epw_df[ColNames.UTCI_NO_SUN_NO_WIND], bins=utci_bins, labels=utci_labels - ) - epw_df[ColNames.UTCI_SUN_WIND_CATEGORIES] = pd.cut( - x=epw_df[ColNames.UTCI_SUN_WIND], bins=utci_bins, labels=utci_labels - ) - epw_df[ColNames.UTCI_SUN_NOWIND_CATEGORIES] = pd.cut( - x=epw_df[ColNames.UTCI_SUN_NO_WIND], bins=utci_bins, labels=utci_labels - ) # Add psy values ta_rh = np.vectorize(psy.psy_ta_rh)(epw_df[ColNames.DBT], epw_df[ColNames.RH]) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index c57fd22d..24e34b8f 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -843,6 +843,14 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): ) return fig +def time_filtering(df, start_time, end_time, time_col, target_col): + + if start_time <= end_time: + mask = (df[time_col] < start_time) | (df[time_col] > end_time) + else: + mask = (df[time_col] >= end_time) & (df[time_col] <= start_time) + df.loc[mask, target_col] = None + return df def filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, var @@ -852,21 +860,10 @@ def filter_df_by_month_and_hour( ) if time_filter: - if start_month <= end_month: - mask = (df[ColNames.MONTH] < start_month) | (df[ColNames.MONTH] > end_month) - df.loc[mask, var] = None - else: - mask = (df[ColNames.MONTH] >= end_month) & ( - df[ColNames.MONTH] <= start_month - ) - df.loc[mask, var] = None - - if start_hour <= end_hour: - mask = (df[ColNames.HOUR] <= start_hour) | (df[ColNames.HOUR] > end_hour) - df.loc[mask, var] = None - else: - mask = (df[ColNames.HOUR] > end_hour) & (df[ColNames.HOUR] <= start_hour) - df.loc[mask, var] = None + # Month filter + time_filtering(df, start_month, end_month, ColNames.MONTH, var) + # Hour filter + time_filtering(df, start_hour, end_hour, ColNames.HOUR, var) return df From edb73dfc770ed8e117b68a5cca8600b54bbbcc06 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Tue, 2 Sep 2025 17:46:25 +1000 Subject: [PATCH 068/163] Refactor: extract reusable functions for temperature conversion, default value lists, and data range calculations --- pages/lib/charts_data_explorer.py | 14 ++++++++------ pages/lib/charts_sun.py | 14 +++++++++----- pages/lib/extract_df.py | 26 ++++++++++++++------------ pages/lib/template_graphs.py | 26 ++++++++++++++------------ pages/lib/utils.py | 7 +++++++ pages/natural_ventilation.py | 8 ++++++-- pages/psy-chart.py | 13 ++++++++----- 7 files changed, 66 insertions(+), 42 deletions(-) diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index c747763f..848152fd 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -1,9 +1,11 @@ -from math import ceil, floor - import numpy as np import math import plotly.express as px import plotly.graph_objects as go +from pages.lib.utils import ( + get_data_max, + get_data_min, +) from pages.lib.global_scheme import template, mapping_dictionary, month_lst from pages.lib.global_column_names import ColNames @@ -43,8 +45,8 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si range_z = var_range else: # Set maximum and minimum according to data - data_max = 5 * ceil(df[var].max() / 5) - data_min = 5 * floor(df[var].min() / 5) + data_max = get_data_max(df[var]) + data_min = get_data_min(df[var]) range_z = [data_min, data_max] title = var_name + " (" + var_unit + ")" @@ -121,8 +123,8 @@ def three_var_graph( if global_local != "global": # Set maximum and minimum according to data - data_max = 5 * math.ceil(df[var].max() / 5) - data_min = 5 * math.floor(df[var].min() / 5) + data_max = get_data_max(df[var]) + data_min = get_data_min(df[var]) var_range = [data_min, data_max] color_scale = var_color diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index c6449ddc..309fec1a 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -1,11 +1,15 @@ from datetime import timedelta -from math import ceil, cos, floor, radians +from math import cos, radians import numpy as np import pandas as pd import plotly.graph_objects as go from config import UnitSystem +from pages.lib.utils import ( + get_data_max, + get_data_min, +) from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -135,8 +139,8 @@ def polar_graph(df, meta, global_local, var, si_ip): range_z = var_range else: # Set maximum and minimum according to data - data_max = 5 * ceil(solpos[var].max() / 5) - data_min = 5 * floor(solpos[var].min() / 5) + data_max = get_data_max(solpos[var]) + data_min = get_data_min(solpos[var]) range_z = [data_min, data_max] tz = "UTC" @@ -348,8 +352,8 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): range_z = var_range else: # Set maximum and minimum according to data - data_max = 5 * ceil(df[var].max() / 5) - data_min = 5 * floor(df[var].min() / 5) + data_max = get_data_max(df[var]) + data_min = get_data_min(df[var]) range_z = [data_min, data_max] if var == "None": diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index f3fb8112..99681083 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -279,14 +279,14 @@ def create_df(lst, file_name): # Add in UTCI sol_altitude = epw_df[ColNames.ELEVATION].mask(epw_df[ColNames.ELEVATION] <= 0, 0) - sharp = [45] * 8760 + sharp = check_value(45) sol_radiation_dir = epw_df[ColNames.DIR_NOR_RAD] - sol_transmittance = [1] * 8760 # CHECK VALUE - f_svv = [1] * 8760 # CHECK VALUE - f_bes = [1] * 8760 # CHECK VALUE - asw = [0.7] * 8760 # CHECK VALUE - posture = ["standing"] * 8760 - floor_reflectance = [0.6] * 8760 # EXPOSE AS A VARIABLE? + sol_transmittance = check_value(1) # CHECK VALUE + f_svv = check_value(1) # CHECK VALUE + f_bes = check_value(1) # CHECK VALUE + asw = check_value(0.7) # CHECK VALUE + posture = check_value("standing") + floor_reflectance = check_value(0.6) # EXPOSE AS A VARIABLE? mrt = np.vectorize(solar_gain)( sol_altitude, @@ -411,11 +411,11 @@ def enthalpy(df, name): def convert_data(df, mapping_json): - df[ColNames.ADAPTIVE_COMFORT] = df[ColNames.ADAPTIVE_COMFORT] * 1.8 + 32 - df[ColNames.ADAPTIVE_CMF_80_LOW] = df[ColNames.ADAPTIVE_CMF_80_LOW] * 1.8 + 32 - df[ColNames.ADAPTIVE_CMF_80_UP] = df[ColNames.ADAPTIVE_CMF_80_UP] * 1.8 + 32 - df[ColNames.ADAPTIVE_CMF_90_LOW] = df[ColNames.ADAPTIVE_CMF_90_LOW] * 1.8 + 32 - df[ColNames.ADAPTIVE_CMF_90_UP] = df[ColNames.ADAPTIVE_CMF_90_UP] * 1.8 + 32 + temperature(df, ColNames.ADAPTIVE_COMFORT) + temperature(df, ColNames.ADAPTIVE_CMF_80_LOW) + temperature(df, ColNames.ADAPTIVE_CMF_80_UP) + temperature(df, ColNames.ADAPTIVE_CMF_90_LOW) + temperature(df, ColNames.ADAPTIVE_CMF_90_UP) mapping_dict = json.loads(mapping_json) for key in json.loads(mapping_json): @@ -426,6 +426,8 @@ def convert_data(df, mapping_json): conversion_function(df, key) return json.dumps(mapping_dict) +def check_value(value, hours=8760): + return [value] * hours if __name__ == "__main__": # fmt: off diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 24e34b8f..d978d10d 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -1,11 +1,13 @@ -from math import ceil, floor - import numpy as np import pandas as pd import plotly.graph_objects as go from plotly.subplots import make_subplots from config import UnitSystem +from pages.lib.utils import ( + get_data_max, + get_data_min, +) from pages.lib.global_scheme import mapping_dictionary import dash_bootstrap_components as dbc from .global_scheme import month_lst, template, tight_margins @@ -25,8 +27,8 @@ def violin(df, var, global_local, si_ip): data_night = df.loc[mask_night, var] if global_local != "global": - data_max = 5 * ceil(df[var].max() / 5) - data_min = 5 * floor(df[var].min() / 5) + data_max = get_data_max(df[var]) + data_min = get_data_min(df[var]) var_range = [data_min, data_max] fig = go.Figure() @@ -95,8 +97,8 @@ def yearly_profile(df, var, global_local, si_ip): range_y = var_range else: # Set maximum and minimum according to data - data_max = 5 * ceil(df[var].max() / 5) - data_min = 5 * floor(df[var].min() / 5) + data_max = get_data_max(df[var]) + data_min = get_data_min(df[var]) range_y = [data_min, data_max] var_single_color = var_color[len(var_color) // 2] @@ -255,8 +257,8 @@ def daily_profile(df, var, global_local, si_ip): range_y = var_range else: # Set maximum and minimum according to data - data_max = 5 * ceil(df[var].max() / 5) - data_min = 5 * floor(df[var].min() / 5) + data_max = get_data_max(df[var]) + data_min = get_data_min(df[var]) range_y = [data_min, data_max] var_single_color = var_color[len(var_color) // 2] @@ -371,8 +373,8 @@ def heatmap_with_filter( range_z = var_range else: # Set maximum and minimum according to data - data_max = 5 * ceil(df[var].max() / 5) - data_min = 5 * floor(df[var].min() / 5) + data_max = get_data_max(df[var]) + data_min = get_data_min(df[var]) range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( @@ -430,8 +432,8 @@ def heatmap(df, var, global_local, si_ip): range_z = var_range else: # Set maximum and minimum according to data - data_max = 5 * ceil(df[var].max() / 5) - data_min = 5 * floor(df[var].min() / 5) + data_max = get_data_max(df[var]) + data_min = get_data_min(df[var]) range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 2455b144..505d740a 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -1,6 +1,7 @@ import copy import functools import time +import math import dash_bootstrap_components as dbc import pandas as pd @@ -289,3 +290,9 @@ def dropdown(options=None, **kwargs): clearable=False, **kwargs, ) + +def get_data_max(series, base=5): + return base * math.ceil(series.max() / base) + +def get_data_min(series, base=5): + return base * math.floor(series.min() / base) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 1f5f7ce7..5fa8190e 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -17,6 +17,10 @@ container_row_center_full, container_col_center_one_of_three, ) +from pages.lib.utils import ( + get_data_max, + get_data_min, +) from pages.lib.template_graphs import filter_df_by_month_and_hour from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds @@ -373,8 +377,8 @@ def nv_heatmap( if global_local == "global": range_z = var_range else: - data_max = 5 * math.ceil(df[var].max() / 5) - data_min = 5 * math.floor(df[var].min() / 5) + data_max = get_data_max(df[var]) + data_min = get_data_min(df[var]) range_z = [data_min, data_max] title = ( diff --git a/pages/psy-chart.py b/pages/psy-chart.py index fc49e8f2..dc56c85d 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -1,5 +1,3 @@ -from math import ceil, floor - import dash from dash import dcc, html import dash_bootstrap_components as dbc @@ -12,6 +10,11 @@ from pythermalcomfort import psychrometrics as psy from config import PageUrls, DocLinks, PageInfo, UnitSystem +from pages.lib.utils import ( + get_data_max, + get_data_min, +) +from pages.lib.extract_df import temperature from pages.lib.global_element_ids import ElementIds from pages.lib.global_column_names import ColNames from pages.lib.global_id_buttons import IdButtons @@ -321,8 +324,8 @@ def update_psych_chart( else: # Set maximum and minimum according to data - data_max = 5 * ceil(df[ColNames.DBT].max() / 5) - data_min = 5 * floor(df[ColNames.DBT].min() / 5) + data_max = get_data_max(df[ColNames.DBT]) + data_min = get_data_min(df[ColNames.DBT]) var_range_x = [data_min, data_max] data_max = round(df[ColNames.HR].max(), 4) @@ -358,7 +361,7 @@ def update_psych_chart( if si_ip == UnitSystem.IP: for j in range(len(dbt_list)): - dbt_list_convert[j] = dbt_list_convert[j] * 1.8 + 32 + temperature(dbt_list_convert, j) fig.add_trace( go.Scatter( From 2be6e2d05b98daf6f141787579474009ecbada51 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Tue, 2 Sep 2025 18:08:30 +1000 Subject: [PATCH 069/163] style: format code using ruff --- Pipfile | 62 +++- Pipfile.lock | 548 +++++++++++++++++++++++------- pages/lib/charts_data_explorer.py | 1 - pages/lib/extract_df.py | 39 ++- pages/lib/template_graphs.py | 3 +- pages/lib/utils.py | 2 + pages/natural_ventilation.py | 2 - 7 files changed, 518 insertions(+), 139 deletions(-) diff --git a/Pipfile b/Pipfile index 91288833..6f6cf397 100644 --- a/Pipfile +++ b/Pipfile @@ -4,18 +4,66 @@ verify_ssl = true name = "pypi" [packages] -dash = "==2.15" -pvlib = "==0.9.1" -pythermalcomfort = "==2.9.1" +pre-commit = "*" +ansi2html = "==1.9.2" +black = "==25.1.0" +blinker = "==1.9.0" +bump2version = "==1.0.1" +cachelib = "==0.9.0" +certifi = "==2025.7.14" +charset-normalizer = "==3.4.2" +cleanpy = "==0.5.1" +click = "==8.2.1" +dash = "==2.15.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" -requests = "==2.32.4" -plotly = "==5.18.0" -pandas = "==2.2.0" +dash-table = "==5.0.0" +dataclass-wizard = "==0.22.3" +editorconfig = "==0.17.1" +flask = "==2.3.3" +flask-caching = "==2.0.2" +h5py = "==3.14.0" +idna = "==3.10" +importlib-metadata = "==8.7.0" +iniconfig = "==2.1.0" +itsdangerous = "==2.2.0" +jinja2 = "==3.1.6" +jsbeautifier = "==1.15.4" +llvmlite = "==0.44.0" +markupsafe = "==3.0.2" +more-itertools = "==9.1.0" +mypy-extensions = "==1.1.0" +narwhals = "==2.0.1" +nest-asyncio = "==1.6.0" +numba = "==0.61.2" numpy = "==1.26.3" -dash-iconify = "*" +packaging = "==25.0" +pandas = "==2.2.0" +pathspec = "==0.12.1" +platformdirs = "==4.3.8" +plotly = "==5.18.0" +pluggy = "==1.6.0" +pvlib = "==0.9.1" +pygments = "==2.19.2" +pytest = "==8.4.1" +pythermalcomfort = "==2.9.1" +python-dateutil = "==2.9.0.post0" +pytz = "==2025.2" +requests = "==2.32.4" +retrying = "==1.4.1" +ruff = "==0.12.7" scipy = "==1.12.0" +six = "==1.17.0" +tenacity = "==9.1.2" +typing-extensions = "==4.14.1" +tzdata = "==2025.2" +urllib3 = "==2.5.0" +werkzeug = "==3.0.6" +zipp = "==3.23.0" [dev-packages] cleanpy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 15c208f9..72cd8128 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7214a1158f64483648ecff36a57b61b67020408e0f16770b77d7165a9d6f47e0" + "sha256": "341e878aaca3828643931f1288597cb2f6cc4aa34cb18972ac9d7954272df6c3" }, "pipfile-spec": 6, "requires": { @@ -16,131 +16,205 @@ ] }, "default": { + "ansi2html": { + "hashes": [ + "sha256:3453bf87535d37b827b05245faaa756dbab4ec3d69925e352b6319c3c955c0a5", + "sha256:dccb75aa95fb018e5d299be2b45f802952377abfdce0504c17a6ee6ef0a420c5" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==1.9.2" + }, + "black": { + "hashes": [ + "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", + "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", + "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", + "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", + "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", + "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", + "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", + "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", + "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", + "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", + "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", + "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", + "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", + "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", + "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", + "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", + "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", + "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", + "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", + "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", + "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", + "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==25.1.0" + }, "blinker": { "hashes": [ "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==1.9.0" }, + "bump2version": { + "hashes": [ + "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", + "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" + ], + "index": "pypi", + "markers": "python_version >= '3.5'", + "version": "==1.0.1" + }, "cachelib": { "hashes": [ "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5", "sha256:811ceeb1209d2fe51cd2b62810bd1eccf70feba5c52641532498be5c675493b3" ], + "index": "pypi", "markers": "python_version >= '3.7'", "version": "==0.9.0" }, "certifi": { "hashes": [ - "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", - "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" + "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", + "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" ], + "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2025.8.3" + "version": "==2025.7.14" + }, + "cfgv": { + "hashes": [ + "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", + "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" }, "charset-normalizer": { "hashes": [ - "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", - "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", - "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", - "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", - "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", - "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", - "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", - "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", - "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", - "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", - "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", - "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", - "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", - "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", - "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", - "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", - "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", - "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", - "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", - "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", - "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", - "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", - "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", - "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", - "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", - "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", - "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", - "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", - "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", - "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", - "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", - "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", - "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", - "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", - "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", - "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", - "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", - "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", - "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", - "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", - "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", - "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", - "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", - "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", - "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", - "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", - "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", - "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", - "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", - "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", - "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", - "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", - "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", - "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", - "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", - "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", - "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", - "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", - "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", - "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", - "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", - "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", - "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", - "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", - "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", - "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", - "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", - "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", - "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", - "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", - "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", - "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", - "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", - "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", - "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", - "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", - "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", - "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", - "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" + "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", + "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", + "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", + "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", + "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", + "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", + "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", + "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", + "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", + "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", + "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", + "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", + "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", + "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", + "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", + "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", + "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", + "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", + "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", + "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", + "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", + "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", + "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", + "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", + "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", + "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", + "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", + "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", + "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", + "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", + "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", + "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", + "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", + "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", + "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", + "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", + "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", + "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", + "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", + "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", + "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", + "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", + "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", + "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", + "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", + "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", + "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", + "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", + "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", + "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", + "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", + "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", + "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", + "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", + "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", + "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", + "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", + "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", + "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", + "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", + "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", + "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", + "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", + "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", + "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", + "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", + "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", + "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", + "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", + "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", + "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", + "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", + "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", + "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", + "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", + "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", + "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", + "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", + "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", + "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", + "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", + "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", + "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", + "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", + "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", + "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", + "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", + "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", + "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", + "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", + "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", + "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" ], + "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==3.4.3" + "version": "==3.4.2" + }, + "cleanpy": { + "hashes": [ + "sha256:9ddfa7ce80dd888b597a8b0bfeea3b69567839b6f41b775a4f76f46914d5170e", + "sha256:c60589d5da68527ca0c9151e28ed56fffae69df4ab6c9bfd8c1cf1d9e76a09b8" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==0.5.1" }, "click": { "hashes": [ "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" ], + "index": "pypi", "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", @@ -164,6 +238,7 @@ "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346", "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee" ], + "index": "pypi", "version": "==2.0.0" }, "dash-extensions": { @@ -180,6 +255,7 @@ "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50", "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63" ], + "index": "pypi", "version": "==2.0.0" }, "dash-iconify": { @@ -203,6 +279,7 @@ "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308", "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9" ], + "index": "pypi", "version": "==5.0.0" }, "dataclass-wizard": { @@ -210,21 +287,39 @@ "sha256:4c46591782265058f1148cfd1f54a3a91221e63986fdd04c9d59f4ced61f4424", "sha256:63751203e54b9b9349212cc185331da73c1adc99c51312575eb73bb5c00c1962" ], + "index": "pypi", "version": "==0.22.3" }, + "distlib": { + "hashes": [ + "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", + "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d" + ], + "version": "==0.4.0" + }, "editorconfig": { "hashes": [ "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", "sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==0.17.1" }, + "filelock": { + "hashes": [ + "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", + "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d" + ], + "markers": "python_version >= '3.9'", + "version": "==3.19.1" + }, "flask": { "hashes": [ "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc", "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==2.3.3" }, @@ -233,6 +328,7 @@ "sha256:19571f2570e9b8dd9dd9d2f49d7cbee69c14ebe8cc001100b1eb98c379dd80ad", "sha256:24b60c552d59a9605cc1b6a42c56cdb39a82a28dab4532bbedb9222ae54ecb4e" ], + "index": "pypi", "markers": "python_version >= '3.7'", "version": "==2.0.2" }, @@ -265,14 +361,24 @@ "sha256:f30dbc58f2a0efeec6c8836c97f6c94afd769023f44e2bb0ed7b17a16ec46088", "sha256:f5cc1601e78027cedfec6dd50efb4802f018551754191aeb58d948bd3ec3bd7a" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==3.14.0" }, + "identify": { + "hashes": [ + "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", + "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32" + ], + "markers": "python_version >= '3.9'", + "version": "==2.6.13" + }, "idna": { "hashes": [ "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], + "index": "pypi", "markers": "python_version >= '3.6'", "version": "==3.10" }, @@ -281,14 +387,25 @@ "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==8.7.0" }, + "iniconfig": { + "hashes": [ + "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", + "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.1.0" + }, "itsdangerous": { "hashes": [ "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==2.2.0" }, @@ -297,6 +414,7 @@ "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], + "index": "pypi", "markers": "python_version >= '3.7'", "version": "==3.1.6" }, @@ -305,6 +423,7 @@ "sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592", "sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528" ], + "index": "pypi", "version": "==1.15.4" }, "llvmlite": { @@ -331,6 +450,7 @@ "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610" ], + "index": "pypi", "markers": "python_version >= '3.10'", "version": "==0.44.0" }, @@ -398,6 +518,7 @@ "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==3.0.2" }, @@ -406,17 +527,45 @@ "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d", "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3" ], + "index": "pypi", "markers": "python_version >= '3.7'", "version": "==9.1.0" }, + "mypy-extensions": { + "hashes": [ + "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", + "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==1.1.0" + }, + "narwhals": { + "hashes": [ + "sha256:235e61ca807bc21110ca36a4d53888ecc22c42dcdf50a7c886e10dde3fd7f38c", + "sha256:837457e36a2ba1710c881fb69e1f79ce44fb81728c92ac378f70892a53af8ddb" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==2.0.1" + }, "nest-asyncio": { "hashes": [ "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c" ], + "index": "pypi", "markers": "python_version >= '3.5'", "version": "==1.6.0" }, + "nodeenv": { + "hashes": [ + "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", + "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" + ], + "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": "==1.9.1" + }, "numba": { "hashes": [ "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", @@ -441,6 +590,7 @@ "sha256:ea0247617edcb5dd61f6106a56255baab031acc4257bddaeddb3a1003b4ca3fd", "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2" ], + "index": "pypi", "markers": "python_version >= '3.10'", "version": "==0.61.2" }, @@ -492,6 +642,7 @@ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==25.0" }, @@ -531,6 +682,24 @@ "markers": "python_version >= '3.9'", "version": "==2.2.0" }, + "pathspec": { + "hashes": [ + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==0.12.1" + }, + "platformdirs": { + "hashes": [ + "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", + "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==4.3.8" + }, "plotly": { "hashes": [ "sha256:23aa8ea2f4fb364a20d34ad38235524bd9d691bf5299e800bca608c31e8db8de", @@ -540,6 +709,24 @@ "markers": "python_version >= '3.6'", "version": "==5.18.0" }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pre-commit": { + "hashes": [ + "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", + "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==4.3.0" + }, "pvlib": { "hashes": [ "sha256:ead96f47898befd7728ab0b61b9747231008e151ef78a26d5e41d0b6a95a3a9d", @@ -549,6 +736,24 @@ "markers": "python_version >= '3.6'", "version": "==0.9.1" }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", + "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==8.4.1" + }, "pythermalcomfort": { "hashes": [ "sha256:607995f6920a03911c7b9fddd06d819db1fa6e658d742b4ec8395a2c90707da5", @@ -563,7 +768,8 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, "pytz": { @@ -571,8 +777,68 @@ "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" ], + "index": "pypi", "version": "==2025.2" }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, "requests": { "hashes": [ "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", @@ -584,11 +850,37 @@ }, "retrying": { "hashes": [ - "sha256:bbc004aeb542a74f3569aeddf42a2516efefcdaff90df0eb38fbfbf19f179f59", - "sha256:d102e75d53d8d30b88562d45361d6c6c934da06fab31bd81c0420acb97a8ba39" + "sha256:4d206e0ed2aff5ef2f3cd867abb9511e9e8f31127c5aca20f1d5246e476903b0", + "sha256:d736050c1adfc0a71fa022d9198ee130b0e66be318678a3fdd8b1b8872dc0997" ], + "index": "pypi", "markers": "python_version >= '3.6'", - "version": "==1.4.2" + "version": "==1.4.1" + }, + "ruff": { + "hashes": [ + "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", + "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", + "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", + "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", + "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", + "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", + "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", + "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", + "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", + "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", + "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", + "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", + "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", + "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", + "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", + "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", + "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", + "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.12.7" }, "scipy": { "hashes": [ @@ -635,7 +927,8 @@ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "index": "pypi", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.17.0" }, "tenacity": { @@ -643,6 +936,7 @@ "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==9.1.2" }, @@ -651,6 +945,7 @@ "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==4.14.1" }, @@ -659,6 +954,7 @@ "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" ], + "index": "pypi", "markers": "python_version >= '2'", "version": "==2025.2" }, @@ -667,14 +963,24 @@ "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==2.5.0" }, + "virtualenv": { + "hashes": [ + "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", + "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a" + ], + "markers": "python_version >= '3.8'", + "version": "==20.34.0" + }, "werkzeug": { "hashes": [ "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17", "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==3.0.6" }, @@ -683,6 +989,7 @@ "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==3.23.0" } @@ -748,6 +1055,7 @@ "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" ], + "index": "pypi", "markers": "python_version >= '3.10'", "version": "==8.2.1" }, @@ -779,6 +1087,7 @@ "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==2.1.0" }, @@ -787,6 +1096,7 @@ "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==1.1.0" }, @@ -803,6 +1113,7 @@ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==25.0" }, @@ -811,6 +1122,7 @@ "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==0.12.1" }, @@ -819,6 +1131,7 @@ "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==4.3.8" }, @@ -827,6 +1140,7 @@ "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" ], + "index": "pypi", "markers": "python_version >= '3.9'", "version": "==1.6.0" }, @@ -844,6 +1158,7 @@ "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" ], + "index": "pypi", "markers": "python_version >= '3.8'", "version": "==2.19.2" }, @@ -917,29 +1232,28 @@ }, "ruff": { "hashes": [ - "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", - "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", - "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", - "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", - "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", - "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", - "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", - "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", - "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", - "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", - "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", - "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", - "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", - "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", - "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", - "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", - "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", - "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", - "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e" + "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", + "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", + "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", + "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", + "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", + "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", + "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", + "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", + "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", + "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", + "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", + "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", + "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", + "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", + "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", + "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", + "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", + "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8" ], "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==0.12.9" + "version": "==0.12.7" }, "virtualenv": { "hashes": [ diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index 848152fd..c9fdbb23 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -1,5 +1,4 @@ import numpy as np -import math import plotly.express as px import plotly.graph_objects as go from pages.lib.utils import ( diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 99681083..2807db24 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -76,14 +76,17 @@ def get_location_info(lst, file_name): return location_info + # ==== Unified UTCI computation and binning ==== -UTCI_BINS = [-999, -40, -27, -13, 0, 9, 26, 32, 38, 46, 999] +UTCI_BINS = [-999, -40, -27, -13, 0, 9, 26, 32, 38, 46, 999] UTCI_LABELS = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] + def utci_calc(df: pd.DataFrame, t_air_col, t_rad_col, wind_col, rh_col=ColNames.RH): """Call utci() using values from df columns.""" return utci(df[t_air_col], df[t_rad_col], df[wind_col], df[rh_col]) + def add_utci_variants(df: pd.DataFrame) -> pd.DataFrame: """ Generate the four UTCI variants: @@ -93,22 +96,35 @@ def add_utci_variants(df: pd.DataFrame) -> pd.DataFrame: - Sun_noWind : DBT + MRT + wind_speed_utci_0 """ recipes = { - ColNames.UTCI_NO_SUN_WIND: (ColNames.DBT, ColNames.DBT, ColNames.WIND_SPEED_UTCI), - ColNames.UTCI_NO_SUN_NO_WIND: (ColNames.DBT, ColNames.DBT, ColNames.WIND_SPEED_UTCI_0), - ColNames.UTCI_SUN_WIND: (ColNames.DBT, ColNames.MRT, ColNames.WIND_SPEED_UTCI), - ColNames.UTCI_SUN_NO_WIND: (ColNames.DBT, ColNames.MRT, ColNames.WIND_SPEED_UTCI_0), + ColNames.UTCI_NO_SUN_WIND: ( + ColNames.DBT, + ColNames.DBT, + ColNames.WIND_SPEED_UTCI, + ), + ColNames.UTCI_NO_SUN_NO_WIND: ( + ColNames.DBT, + ColNames.DBT, + ColNames.WIND_SPEED_UTCI_0, + ), + ColNames.UTCI_SUN_WIND: (ColNames.DBT, ColNames.MRT, ColNames.WIND_SPEED_UTCI), + ColNames.UTCI_SUN_NO_WIND: ( + ColNames.DBT, + ColNames.MRT, + ColNames.WIND_SPEED_UTCI_0, + ), } for out_col, (t_air, t_rad, wind) in recipes.items(): df[out_col] = utci_calc(df, t_air, t_rad, wind) return df + def add_utci_categories(df: pd.DataFrame) -> pd.DataFrame: """Bin the four UTCI columns into categories.""" mapping = { - ColNames.UTCI_NO_SUN_WIND: ColNames.UTCI_NOSUN_WIND_CATEGORIES, + ColNames.UTCI_NO_SUN_WIND: ColNames.UTCI_NOSUN_WIND_CATEGORIES, ColNames.UTCI_NO_SUN_NO_WIND: ColNames.UTCI_NOSUN_NOWIND_CATEGORIES, - ColNames.UTCI_SUN_WIND: ColNames.UTCI_SUN_WIND_CATEGORIES, - ColNames.UTCI_SUN_NO_WIND: ColNames.UTCI_SUN_NOWIND_CATEGORIES, + ColNames.UTCI_SUN_WIND: ColNames.UTCI_SUN_WIND_CATEGORIES, + ColNames.UTCI_SUN_NO_WIND: ColNames.UTCI_SUN_NOWIND_CATEGORIES, } for src_col, dst_col in mapping.items(): df[dst_col] = pd.cut(x=df[src_col], bins=UTCI_BINS, labels=UTCI_LABELS) @@ -318,12 +334,11 @@ def create_df(lst, file_name): epw_df[ColNames.WIND_SPEED_UTCI_0] = epw_df[ColNames.WIND_SPEED_UTCI].mask( epw_df[ColNames.WIND_SPEED_UTCI] >= 0, 0.5 ) - + epw_df = add_utci_variants(epw_df) epw_df = add_utci_categories(epw_df) - # Add psy values ta_rh = np.vectorize(psy.psy_ta_rh)(epw_df[ColNames.DBT], epw_df[ColNames.RH]) psy_df = pd.DataFrame.from_records(ta_rh) @@ -426,8 +441,10 @@ def convert_data(df, mapping_json): conversion_function(df, key) return json.dumps(mapping_dict) + def check_value(value, hours=8760): - return [value] * hours + return [value] * hours + if __name__ == "__main__": # fmt: off diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index d978d10d..bca80de0 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -845,8 +845,8 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): ) return fig -def time_filtering(df, start_time, end_time, time_col, target_col): +def time_filtering(df, start_time, end_time, time_col, target_col): if start_time <= end_time: mask = (df[time_col] < start_time) | (df[time_col] > end_time) else: @@ -854,6 +854,7 @@ def time_filtering(df, start_time, end_time, time_col, target_col): df.loc[mask, target_col] = None return df + def filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, var ): diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 505d740a..7368e7a1 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -291,8 +291,10 @@ def dropdown(options=None, **kwargs): **kwargs, ) + def get_data_max(series, base=5): return base * math.ceil(series.max() / base) + def get_data_min(series, base=5): return base * math.floor(series.min() / base) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 5fa8190e..919143af 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,5 +1,3 @@ -import math - import dash from dash import dcc, html import dash_bootstrap_components as dbc From f7edaf350a18e40892c9d34b19622696e2604780 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Tue, 2 Sep 2025 18:41:55 +1000 Subject: [PATCH 070/163] docs: remove duplicated code style section in contributing.md --- docs/contributing/contributing.md | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 2f9007e3..3018a69f 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -98,25 +98,6 @@ More information about pre-commit hooks can be found [here](https://pre-commit.c Install Black: -We use ruff to enforce the code style and code formatting. You can run it with: - -```bash -pipenv run ruff check . -pipenv run ruff format . -``` - -To ensure that the code is formatted correctly, we use a pre-commit hook that runs Ruff before every commit. -Run the following once to enable hooks in your local repo: - -```bash -pipenv run pre-commit install -# optional: run on all files -pipenv run pre-commit run --all-files -``` - -Hence, you will need to make sure that the code is formatted correctly before committing your changes; otherwise, the commit will fail. -More information about pre-commit hooks can be found [here](https://pre-commit.com/). - ```bash pipenv install black ``` From 1c55ec72b9822e5ceb9a130f00e8c8a9ae185bd3 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Wed, 3 Sep 2025 15:47:02 +1000 Subject: [PATCH 071/163] refactor: Rename functions and add docstrings/type hints for clarity --- Pipfile | 67 +--- Pipfile.lock | 497 ++++++------------------------ pages/lib/charts_data_explorer.py | 11 +- pages/lib/charts_sun.py | 11 +- pages/lib/extract_df.py | 25 +- pages/lib/template_graphs.py | 46 ++- pages/lib/utils.py | 15 +- pages/natural_ventilation.py | 8 +- pages/psy-chart.py | 8 +- 9 files changed, 167 insertions(+), 521 deletions(-) diff --git a/Pipfile b/Pipfile index 6f6cf397..d6f89d9e 100644 --- a/Pipfile +++ b/Pipfile @@ -4,73 +4,20 @@ verify_ssl = true name = "pypi" [packages] -pre-commit = "*" -ansi2html = "==1.9.2" -black = "==25.1.0" -blinker = "==1.9.0" -bump2version = "==1.0.1" -cachelib = "==0.9.0" -certifi = "==2025.7.14" -charset-normalizer = "==3.4.2" -cleanpy = "==0.5.1" -click = "==8.2.1" -dash = "==2.15.0" +dash = "==2.15" +pvlib = "==0.9.1" +pythermalcomfort = "==2.9.1" 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-table = "==5.0.0" -dataclass-wizard = "==0.22.3" -editorconfig = "==0.17.1" -flask = "==2.3.3" -flask-caching = "==2.0.2" -h5py = "==3.14.0" -idna = "==3.10" -importlib-metadata = "==8.7.0" -iniconfig = "==2.1.0" -itsdangerous = "==2.2.0" -jinja2 = "==3.1.6" -jsbeautifier = "==1.15.4" -llvmlite = "==0.44.0" -markupsafe = "==3.0.2" -more-itertools = "==9.1.0" -mypy-extensions = "==1.1.0" -narwhals = "==2.0.1" -nest-asyncio = "==1.6.0" -numba = "==0.61.2" -numpy = "==1.26.3" -packaging = "==25.0" -pandas = "==2.2.0" -pathspec = "==0.12.1" -platformdirs = "==4.3.8" -plotly = "==5.18.0" -pluggy = "==1.6.0" -pvlib = "==0.9.1" -pygments = "==2.19.2" -pytest = "==8.4.1" -pythermalcomfort = "==2.9.1" -python-dateutil = "==2.9.0.post0" -pytz = "==2025.2" requests = "==2.32.4" -retrying = "==1.4.1" -ruff = "==0.12.7" +plotly = "==5.18.0" +pandas = "==2.2.0" +numpy = "==1.26.3" +dash-iconify = "*" scipy = "==1.12.0" -six = "==1.17.0" -tenacity = "==9.1.2" -typing-extensions = "==4.14.1" -tzdata = "==2025.2" -urllib3 = "==2.5.0" -werkzeug = "==3.0.6" -zipp = "==3.23.0" [dev-packages] -cleanpy = "*" -pytest = "*" -bump2version = "*" -black = "*" -ruff = "*" pre-commit = "*" [requires] diff --git a/Pipfile.lock b/Pipfile.lock index 72cd8128..3b8b17ea 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "341e878aaca3828643931f1288597cb2f6cc4aa34cb18972ac9d7954272df6c3" + "sha256": "4fe266b227936bffb73552e6657f642511470c74fa9f3236f6eb0b6c2e7b095c" }, "pipfile-spec": 6, "requires": { @@ -59,7 +59,6 @@ "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc" ], - "index": "pypi", "markers": "python_version >= '3.9'", "version": "==1.9.0" }, @@ -77,18 +76,16 @@ "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5", "sha256:811ceeb1209d2fe51cd2b62810bd1eccf70feba5c52641532498be5c675493b3" ], - "index": "pypi", "markers": "python_version >= '3.7'", "version": "==0.9.0" }, "certifi": { "hashes": [ - "sha256:6b31f564a415d79ee77df69d757bb49a5bb53bd9f756cbbe24394ffd6fc1f4b2", - "sha256:8ea99dbdfaaf2ba2f9bac77b9249ef62ec5218e7c2b2e903378ed5fccf765995" + "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", + "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" ], - "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==2025.7.14" + "version": "==2025.8.3" }, "cfgv": { "hashes": [ @@ -100,102 +97,88 @@ }, "charset-normalizer": { "hashes": [ - "sha256:005fa3432484527f9732ebd315da8da8001593e2cf46a3d817669f062c3d9ed4", - "sha256:046595208aae0120559a67693ecc65dd75d46f7bf687f159127046628178dc45", - "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", - "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", - "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", - "sha256:18dd2e350387c87dabe711b86f83c9c78af772c748904d372ade190b5c7c9d4d", - "sha256:1b1bde144d98e446b056ef98e59c256e9294f6b74d7af6846bf5ffdafd687a7d", - "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", - "sha256:1cad5f45b3146325bb38d6855642f6fd609c3f7cad4dbaf75549bf3b904d3184", - "sha256:21b2899062867b0e1fde9b724f8aecb1af14f2778d69aacd1a5a1853a597a5db", - "sha256:24498ba8ed6c2e0b56d4acbf83f2d989720a93b41d712ebd4f4979660db4417b", - "sha256:25a23ea5c7edc53e0f29bae2c44fcb5a1aa10591aae107f2a2b2583a9c5cbc64", - "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", - "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", - "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", - "sha256:36b31da18b8890a76ec181c3cf44326bf2c48e36d393ca1b72b3f484113ea344", - "sha256:3c21d4fca343c805a52c0c78edc01e3477f6dd1ad7c47653241cf2a206d4fc58", - "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", - "sha256:43e0933a0eff183ee85833f341ec567c0980dae57c464d8a508e1b2ceb336471", - "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", - "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", - "sha256:50bf98d5e563b83cc29471fa114366e6806bc06bc7a25fd59641e41445327836", - "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", - "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", - "sha256:5bf4545e3b962767e5c06fe1738f951f77d27967cb2caa64c28be7c4563e162c", - "sha256:6333b3aa5a12c26b2a4d4e7335a28f1475e0e5e17d69d55141ee3cab736f66d1", - "sha256:65c981bdbd3f57670af8b59777cbfae75364b483fa8a9f420f08094531d54a01", - "sha256:68a328e5f55ec37c57f19ebb1fdc56a248db2e3e9ad769919a58672958e8f366", - "sha256:6a0289e4589e8bdfef02a80478f1dfcb14f0ab696b5a00e1f4b8a14a307a3c58", - "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", - "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", - "sha256:6fc1f5b51fa4cecaa18f2bd7a003f3dd039dd615cd69a2afd6d3b19aed6775f2", - "sha256:70f7172939fdf8790425ba31915bfbe8335030f05b9913d7ae00a87d4395620a", - "sha256:721c76e84fe669be19c5791da68232ca2e05ba5185575086e384352e2c309597", - "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", - "sha256:75d10d37a47afee94919c4fab4c22b9bc2a8bf7d4f46f87363bcf0573f3ff4f5", - "sha256:76af085e67e56c8816c3ccf256ebd136def2ed9654525348cfa744b6802b69eb", - "sha256:770cab594ecf99ae64c236bc9ee3439c3f46be49796e265ce0cc8bc17b10294f", - "sha256:7a6ab32f7210554a96cd9e33abe3ddd86732beeafc7a28e9955cdf22ffadbab0", - "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941", - "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", - "sha256:8075c35cd58273fee266c58c0c9b670947c19df5fb98e7b66710e04ad4e9ff86", - "sha256:8272b73e1c5603666618805fe821edba66892e2870058c94c53147602eab29c7", - "sha256:82d8fd25b7f4675d0c47cf95b594d4e7b158aca33b76aa63d07186e13c0e0ab7", - "sha256:844da2b5728b5ce0e32d863af26f32b5ce61bc4273a9c720a9f3aa9df73b1455", - "sha256:8755483f3c00d6c9a77f490c17e6ab0c8729e39e6390328e42521ef175380ae6", - "sha256:915f3849a011c1f593ab99092f3cecfcb4d65d8feb4a64cf1bf2d22074dc0ec4", - "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", - "sha256:982bb1e8b4ffda883b3d0a521e23abcd6fd17418f6d2c4118d257a10199c0ce3", - "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", - "sha256:9cbfacf36cb0ec2897ce0ebc5d08ca44213af24265bd56eca54bee7923c48fd6", - "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", - "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", - "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", - "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", - "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", - "sha256:aaf27faa992bfee0264dc1f03f4c75e9fcdda66a519db6b957a3f826e285cf12", - "sha256:b2680962a4848b3c4f155dc2ee64505a9c57186d0d56b43123b17ca3de18f0fa", - "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd", - "sha256:b33de11b92e9f75a2b545d6e9b6f37e398d86c3e9e9653c4864eb7e89c5773ef", - "sha256:b3daeac64d5b371dea99714f08ffc2c208522ec6b06fbc7866a450dd446f5c0f", - "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", - "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", - "sha256:c72fbbe68c6f32f251bdc08b8611c7b3060612236e960ef848e0a517ddbe76c5", - "sha256:c9e36a97bee9b86ef9a1cf7bb96747eb7a15c2f22bdb5b516434b00f2a599f02", - "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", - "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", - "sha256:d11b54acf878eef558599658b0ffca78138c8c3655cf4f3a4a673c437e67732e", - "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", - "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", - "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", - "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", - "sha256:dc7039885fa1baf9be153a0626e337aa7ec8bf96b0128605fb0d77788ddc1681", - "sha256:dccab8d5fa1ef9bfba0590ecf4d46df048d18ffe3eec01eeb73a42e0d9e7a8ba", - "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", - "sha256:e45ba65510e2647721e35323d6ef54c7974959f6081b58d4ef5d87c60c84919a", - "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", - "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", - "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", - "sha256:e8082b26888e2f8b36a042a58307d5b917ef2b1cacab921ad3323ef91901c71a", - "sha256:e8323a9b031aa0393768b87f04b4164a40037fb2a3c11ac06a03ffecd3618027", - "sha256:e92fca20c46e9f5e1bb485887d074918b13543b1c2a1185e69bb8d17ab6236a7", - "sha256:eb30abc20df9ab0814b5a2524f23d75dcf83cde762c161917a2b4b7b55b1e518", - "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", - "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", - "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", - "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", - "sha256:f4074c5a429281bf056ddd4c5d3b740ebca4d43ffffe2ef4bf4d2d05114299da", - "sha256:f69a27e45c43520f5487f27627059b64aaf160415589230992cec34c5e18a509", - "sha256:fb707f3e15060adf5b7ada797624a6c6e0138e2a26baa089df64c68ee98e040f", - "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", - "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f" + "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", + "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", + "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", + "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", + "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", + "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", + "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", + "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", + "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", + "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", + "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", + "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", + "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", + "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", + "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", + "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", + "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", + "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", + "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", + "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", + "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", + "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", + "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", + "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", + "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", + "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", + "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", + "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", + "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", + "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", + "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", + "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", + "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", + "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", + "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", + "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", + "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", + "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", + "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", + "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", + "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", + "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", + "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", + "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", + "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", + "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", + "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", + "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", + "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", + "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", + "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", + "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", + "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", + "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", + "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", + "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", + "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", + "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", + "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", + "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", + "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", + "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", + "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", + "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", + "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", + "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", + "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", + "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", + "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", + "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", + "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", + "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", + "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", + "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", + "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", + "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", + "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", + "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", + "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" ], - "index": "pypi", "markers": "python_version >= '3.7'", - "version": "==3.4.2" + "version": "==3.4.3" }, "cleanpy": { "hashes": [ @@ -211,7 +194,6 @@ "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" ], - "index": "pypi", "markers": "python_version >= '3.10'", "version": "==8.2.1" }, @@ -238,7 +220,6 @@ "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346", "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee" ], - "index": "pypi", "version": "==2.0.0" }, "dash-extensions": { @@ -255,7 +236,6 @@ "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50", "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63" ], - "index": "pypi", "version": "==2.0.0" }, "dash-iconify": { @@ -279,7 +259,6 @@ "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308", "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9" ], - "index": "pypi", "version": "==5.0.0" }, "dataclass-wizard": { @@ -287,7 +266,6 @@ "sha256:4c46591782265058f1148cfd1f54a3a91221e63986fdd04c9d59f4ced61f4424", "sha256:63751203e54b9b9349212cc185331da73c1adc99c51312575eb73bb5c00c1962" ], - "index": "pypi", "version": "==0.22.3" }, "distlib": { @@ -302,7 +280,6 @@ "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", "sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745" ], - "index": "pypi", "markers": "python_version >= '3.9'", "version": "==0.17.1" }, @@ -319,7 +296,6 @@ "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc", "sha256:f69fcd559dc907ed196ab9df0e48471709175e696d6e698dd4dbe940f96ce66b" ], - "index": "pypi", "markers": "python_version >= '3.8'", "version": "==2.3.3" }, @@ -328,7 +304,6 @@ "sha256:19571f2570e9b8dd9dd9d2f49d7cbee69c14ebe8cc001100b1eb98c379dd80ad", "sha256:24b60c552d59a9605cc1b6a42c56cdb39a82a28dab4532bbedb9222ae54ecb4e" ], - "index": "pypi", "markers": "python_version >= '3.7'", "version": "==2.0.2" }, @@ -361,7 +336,6 @@ "sha256:f30dbc58f2a0efeec6c8836c97f6c94afd769023f44e2bb0ed7b17a16ec46088", "sha256:f5cc1601e78027cedfec6dd50efb4802f018551754191aeb58d948bd3ec3bd7a" ], - "index": "pypi", "markers": "python_version >= '3.9'", "version": "==3.14.0" }, @@ -378,7 +352,6 @@ "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" ], - "index": "pypi", "markers": "python_version >= '3.6'", "version": "==3.10" }, @@ -387,7 +360,6 @@ "sha256:d13b81ad223b890aa16c5471f2ac3056cf76c5f10f82d6f9292f0b415f389000", "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd" ], - "index": "pypi", "markers": "python_version >= '3.9'", "version": "==8.7.0" }, @@ -405,7 +377,6 @@ "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173" ], - "index": "pypi", "markers": "python_version >= '3.8'", "version": "==2.2.0" }, @@ -414,7 +385,6 @@ "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67" ], - "index": "pypi", "markers": "python_version >= '3.7'", "version": "==3.1.6" }, @@ -423,7 +393,6 @@ "sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592", "sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528" ], - "index": "pypi", "version": "==1.15.4" }, "llvmlite": { @@ -450,7 +419,6 @@ "sha256:eed7d5f29136bda63b6d7804c279e2b72e08c952b7c5df61f45db408e0ee52f3", "sha256:f01a394e9c9b7b1d4e63c327b096d10f6f0ed149ef53d38a09b3749dcf8c9610" ], - "index": "pypi", "markers": "python_version >= '3.10'", "version": "==0.44.0" }, @@ -518,7 +486,6 @@ "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50" ], - "index": "pypi", "markers": "python_version >= '3.9'", "version": "==3.0.2" }, @@ -527,7 +494,6 @@ "sha256:cabaa341ad0389ea83c17a94566a53ae4c9d07349861ecb14dc6d0345cf9ac5d", "sha256:d2bc7f02446e86a68911e58ded76d6561eea00cddfb2a91e7019bbb586c799f3" ], - "index": "pypi", "markers": "python_version >= '3.7'", "version": "==9.1.0" }, @@ -554,7 +520,6 @@ "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c" ], - "index": "pypi", "markers": "python_version >= '3.5'", "version": "==1.6.0" }, @@ -590,7 +555,6 @@ "sha256:ea0247617edcb5dd61f6106a56255baab031acc4257bddaeddb3a1003b4ca3fd", "sha256:efd3db391df53aaa5cfbee189b6c910a5b471488749fd6606c3f33fc984c2ae2" ], - "index": "pypi", "markers": "python_version >= '3.10'", "version": "==0.61.2" }, @@ -642,7 +606,6 @@ "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" ], - "index": "pypi", "markers": "python_version >= '3.8'", "version": "==25.0" }, @@ -768,7 +731,6 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "index": "pypi", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==2.9.0.post0" }, @@ -777,7 +739,6 @@ "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00" ], - "index": "pypi", "version": "==2025.2" }, "pyyaml": { @@ -850,12 +811,11 @@ }, "retrying": { "hashes": [ - "sha256:4d206e0ed2aff5ef2f3cd867abb9511e9e8f31127c5aca20f1d5246e476903b0", - "sha256:d736050c1adfc0a71fa022d9198ee130b0e66be318678a3fdd8b1b8872dc0997" + "sha256:bbc004aeb542a74f3569aeddf42a2516efefcdaff90df0eb38fbfbf19f179f59", + "sha256:d102e75d53d8d30b88562d45361d6c6c934da06fab31bd81c0420acb97a8ba39" ], - "index": "pypi", "markers": "python_version >= '3.6'", - "version": "==1.4.1" + "version": "==1.4.2" }, "ruff": { "hashes": [ @@ -927,7 +887,6 @@ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "index": "pypi", "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.17.0" }, @@ -936,25 +895,22 @@ "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138" ], - "index": "pypi", "markers": "python_version >= '3.9'", "version": "==9.1.2" }, "typing-extensions": { "hashes": [ - "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", - "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" + "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", + "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" ], - "index": "pypi", "markers": "python_version >= '3.9'", - "version": "==4.14.1" + "version": "==4.15.0" }, "tzdata": { "hashes": [ "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9" ], - "index": "pypi", "markers": "python_version >= '2'", "version": "==2025.2" }, @@ -963,7 +919,6 @@ "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc" ], - "index": "pypi", "markers": "python_version >= '3.9'", "version": "==2.5.0" }, @@ -980,7 +935,6 @@ "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17", "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d" ], - "index": "pypi", "markers": "python_version >= '3.8'", "version": "==3.0.6" }, @@ -989,279 +943,8 @@ "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166" ], - "index": "pypi", "markers": "python_version >= '3.9'", "version": "==3.23.0" } - }, - "develop": { - "black": { - "hashes": [ - "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", - "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", - "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", - "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", - "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", - "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", - "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", - "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", - "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", - "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", - "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", - "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", - "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", - "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", - "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", - "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", - "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", - "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", - "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", - "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", - "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", - "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==25.1.0" - }, - "bump2version": { - "hashes": [ - "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", - "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" - ], - "index": "pypi", - "markers": "python_version >= '3.5'", - "version": "==1.0.1" - }, - "cfgv": { - "hashes": [ - "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", - "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" - ], - "markers": "python_version >= '3.8'", - "version": "==3.4.0" - }, - "cleanpy": { - "hashes": [ - "sha256:9ddfa7ce80dd888b597a8b0bfeea3b69567839b6f41b775a4f76f46914d5170e", - "sha256:c60589d5da68527ca0c9151e28ed56fffae69df4ab6c9bfd8c1cf1d9e76a09b8" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==0.5.1" - }, - "click": { - "hashes": [ - "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", - "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" - ], - "index": "pypi", - "markers": "python_version >= '3.10'", - "version": "==8.2.1" - }, - "distlib": { - "hashes": [ - "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", - "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d" - ], - "version": "==0.4.0" - }, - "filelock": { - "hashes": [ - "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", - "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d" - ], - "markers": "python_version >= '3.9'", - "version": "==3.19.1" - }, - "identify": { - "hashes": [ - "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", - "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32" - ], - "markers": "python_version >= '3.9'", - "version": "==2.6.13" - }, - "iniconfig": { - "hashes": [ - "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", - "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.1.0" - }, - "mypy-extensions": { - "hashes": [ - "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", - "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.1.0" - }, - "nodeenv": { - "hashes": [ - "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", - "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" - ], - "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": "==1.9.1" - }, - "packaging": { - "hashes": [ - "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", - "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==25.0" - }, - "pathspec": { - "hashes": [ - "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", - "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==0.12.1" - }, - "platformdirs": { - "hashes": [ - "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", - "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==4.3.8" - }, - "pluggy": { - "hashes": [ - "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", - "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.6.0" - }, - "pre-commit": { - "hashes": [ - "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", - "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==4.3.0" - }, - "pygments": { - "hashes": [ - "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", - "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.19.2" - }, - "pytest": { - "hashes": [ - "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", - "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==8.4.1" - }, - "pyyaml": { - "hashes": [ - "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", - "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", - "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", - "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", - "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", - "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", - "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", - "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", - "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", - "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", - "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", - "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", - "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", - "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", - "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", - "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", - "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", - "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", - "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", - "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", - "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", - "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", - "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", - "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", - "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", - "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", - "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", - "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", - "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", - "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", - "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", - "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", - "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", - "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", - "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", - "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", - "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", - "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", - "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", - "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", - "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", - "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", - "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", - "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", - "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", - "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", - "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", - "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", - "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", - "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", - "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", - "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", - "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" - ], - "markers": "python_version >= '3.8'", - "version": "==6.0.2" - }, - "ruff": { - "hashes": [ - "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", - "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", - "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", - "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", - "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", - "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", - "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", - "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", - "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", - "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", - "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", - "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", - "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", - "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", - "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", - "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", - "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", - "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==0.12.7" - }, - "virtualenv": { - "hashes": [ - "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", - "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a" - ], - "markers": "python_version >= '3.8'", - "version": "==20.34.0" - } } } diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index c9fdbb23..4101271e 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -1,10 +1,7 @@ import numpy as np import plotly.express as px import plotly.graph_objects as go -from pages.lib.utils import ( - get_data_max, - get_data_min, -) +from pages.lib.utils import get_max_min_value from pages.lib.global_scheme import template, mapping_dictionary, month_lst from pages.lib.global_column_names import ColNames @@ -44,8 +41,7 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si range_z = var_range else: # Set maximum and minimum according to data - data_max = get_data_max(df[var]) - data_min = get_data_min(df[var]) + data_max, data_min = get_max_min_value(df[var]) range_z = [data_min, data_max] title = var_name + " (" + var_unit + ")" @@ -122,8 +118,7 @@ def three_var_graph( if global_local != "global": # Set maximum and minimum according to data - data_max = get_data_max(df[var]) - data_min = get_data_min(df[var]) + data_max, data_min = get_max_min_value(df[var]) var_range = [data_min, data_max] color_scale = var_color diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 309fec1a..9bd9747c 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -6,10 +6,7 @@ import plotly.graph_objects as go from config import UnitSystem -from pages.lib.utils import ( - get_data_max, - get_data_min, -) +from pages.lib.utils import get_max_min_value from pages.lib.global_scheme import ( template, mapping_dictionary, @@ -139,8 +136,7 @@ def polar_graph(df, meta, global_local, var, si_ip): range_z = var_range else: # Set maximum and minimum according to data - data_max = get_data_max(solpos[var]) - data_min = get_data_min(solpos[var]) + data_max, data_min = get_max_min_value(solpos[var]) range_z = [data_min, data_max] tz = "UTC" @@ -352,8 +348,7 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): range_z = var_range else: # Set maximum and minimum according to data - data_max = get_data_max(df[var]) - data_min = get_data_min(df[var]) + data_max, data_min = get_max_min_value(df[var]) range_z = [data_min, data_max] if var == "None": diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 2807db24..b91b7402 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -295,14 +295,14 @@ def create_df(lst, file_name): # Add in UTCI sol_altitude = epw_df[ColNames.ELEVATION].mask(epw_df[ColNames.ELEVATION] <= 0, 0) - sharp = check_value(45) + sharp = expand_to_hours(45) sol_radiation_dir = epw_df[ColNames.DIR_NOR_RAD] - sol_transmittance = check_value(1) # CHECK VALUE - f_svv = check_value(1) # CHECK VALUE - f_bes = check_value(1) # CHECK VALUE - asw = check_value(0.7) # CHECK VALUE - posture = check_value("standing") - floor_reflectance = check_value(0.6) # EXPOSE AS A VARIABLE? + sol_transmittance = expand_to_hours(1) # CHECK VALUE + f_svv = expand_to_hours(1) # CHECK VALUE + f_bes = expand_to_hours(1) # CHECK VALUE + asw = expand_to_hours(0.7) # CHECK VALUE + posture = expand_to_hours("standing") + floor_reflectance = expand_to_hours(0.6) # EXPOSE AS A VARIABLE? mrt = np.vectorize(solar_gain)( sol_altitude, @@ -442,7 +442,16 @@ def convert_data(df, mapping_json): return json.dumps(mapping_dict) -def check_value(value, hours=8760): +def expand_to_hours(value, hours=8760): + """Return a list with the input value repeated for a given number of hours. + + Args: + value: The value to repeat. + hours: Number of repetitions. Defaults to 8760 (hours in a year). + + Returns: + A list containing the value repeated `hours` times. + """ return [value] * hours diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index bca80de0..216f0fac 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -4,10 +4,7 @@ from plotly.subplots import make_subplots from config import UnitSystem -from pages.lib.utils import ( - get_data_max, - get_data_min, -) +from pages.lib.utils import get_max_min_value from pages.lib.global_scheme import mapping_dictionary import dash_bootstrap_components as dbc from .global_scheme import month_lst, template, tight_margins @@ -27,8 +24,7 @@ def violin(df, var, global_local, si_ip): data_night = df.loc[mask_night, var] if global_local != "global": - data_max = get_data_max(df[var]) - data_min = get_data_min(df[var]) + data_max, data_min = get_max_min_value(df[var]) var_range = [data_min, data_max] fig = go.Figure() @@ -97,8 +93,7 @@ def yearly_profile(df, var, global_local, si_ip): range_y = var_range else: # Set maximum and minimum according to data - data_max = get_data_max(df[var]) - data_min = get_data_min(df[var]) + data_max, data_min = get_max_min_value(df[var]) range_y = [data_min, data_max] var_single_color = var_color[len(var_color) // 2] @@ -257,8 +252,7 @@ def daily_profile(df, var, global_local, si_ip): range_y = var_range else: # Set maximum and minimum according to data - data_max = get_data_max(df[var]) - data_min = get_data_min(df[var]) + data_max, data_min = get_max_min_value(df[var]) range_y = [data_min, data_max] var_single_color = var_color[len(var_color) // 2] @@ -373,8 +367,7 @@ def heatmap_with_filter( range_z = var_range else: # Set maximum and minimum according to data - data_max = get_data_max(df[var]) - data_min = get_data_min(df[var]) + data_max, data_min = get_max_min_value(df[var]) range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( @@ -432,8 +425,7 @@ def heatmap(df, var, global_local, si_ip): range_z = var_range else: # Set maximum and minimum according to data - data_max = get_data_max(df[var]) - data_min = get_data_min(df[var]) + data_max, data_min = get_max_min_value(df[var]) range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( @@ -847,6 +839,18 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): def time_filtering(df, start_time, end_time, time_col, target_col): + """Mask values in the target column based on the given time range. + + Args: + df: Input dataframe. + start_time: Start of the time range. + end_time: End of the time range. + time_col: Column name representing time (e.g., hour or month). + target_col: Column name to apply the mask on. + + Returns: + A modified DataFrame with masked values outside the given time range. + """ if start_time <= end_time: mask = (df[time_col] < start_time) | (df[time_col] > end_time) else: @@ -858,6 +862,20 @@ def time_filtering(df, start_time, end_time, time_col, target_col): def filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, var ): + """Apply month and hour filtering to the DataFrame based on user selections. + + Args: + df: Input DataFrame. + time_filter: Whether to apply the time filter. + month: Selected month range. + hour: Selected hour range. + invert_month: Whether to invert the month range. + invert_hour: Whether to invert the hour range. + var: Target variable column name. + + Returns: + Filtered DataFrame with appropriate masking applied. + """ start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( month, hour, invert_month, invert_hour ) diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 7368e7a1..8671ba77 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -292,9 +292,16 @@ def dropdown(options=None, **kwargs): ) -def get_data_max(series, base=5): - return base * math.ceil(series.max() / base) +def get_max_min_value(series, base=5): + """Calculate rounded-up max and rounded-down min values based on a base step. + Args: + series: Pandas Series of numeric values. + base: The rounding base. Default is 5. -def get_data_min(series, base=5): - return base * math.floor(series.min() / base) + Returns: + Tuple of (max_value, min_value) adjusted to nearest base step. + """ + data_max = base * math.ceil(series.max() / base) + data_min = base * math.floor(series.min() / base) + return data_max, data_min diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 919143af..ee002e20 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -15,10 +15,7 @@ container_row_center_full, container_col_center_one_of_three, ) -from pages.lib.utils import ( - get_data_max, - get_data_min, -) +from pages.lib.utils import get_max_min_value from pages.lib.template_graphs import filter_df_by_month_and_hour from pages.lib.global_column_names import ColNames from pages.lib.global_element_ids import ElementIds @@ -375,8 +372,7 @@ def nv_heatmap( if global_local == "global": range_z = var_range else: - data_max = get_data_max(df[var]) - data_min = get_data_min(df[var]) + data_max, data_min = get_max_min_value(df[var]) range_z = [data_min, data_max] title = ( diff --git a/pages/psy-chart.py b/pages/psy-chart.py index dc56c85d..c38e7b14 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -10,10 +10,7 @@ from pythermalcomfort import psychrometrics as psy from config import PageUrls, DocLinks, PageInfo, UnitSystem -from pages.lib.utils import ( - get_data_max, - get_data_min, -) +from pages.lib.utils import get_max_min_value from pages.lib.extract_df import temperature from pages.lib.global_element_ids import ElementIds from pages.lib.global_column_names import ColNames @@ -324,8 +321,7 @@ def update_psych_chart( else: # Set maximum and minimum according to data - data_max = get_data_max(df[ColNames.DBT]) - data_min = get_data_min(df[ColNames.DBT]) + data_max, data_min = get_max_min_value(df[ColNames.DBT]) var_range_x = [data_min, data_max] data_max = round(df[ColNames.HR].max(), 4) From d373c5e6b2c152006e3ecc2e8a80c651db23f622 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Wed, 3 Sep 2025 16:57:59 +1000 Subject: [PATCH 072/163] refactor: Add convert_t_to_f as alias to temperature with docstring --- pages/lib/extract_df.py | 23 ++++++++++++++++++----- pages/psy-chart.py | 4 ++-- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index b91b7402..066b9dba 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -426,11 +426,11 @@ def enthalpy(df, name): def convert_data(df, mapping_json): - temperature(df, ColNames.ADAPTIVE_COMFORT) - temperature(df, ColNames.ADAPTIVE_CMF_80_LOW) - temperature(df, ColNames.ADAPTIVE_CMF_80_UP) - temperature(df, ColNames.ADAPTIVE_CMF_90_LOW) - temperature(df, ColNames.ADAPTIVE_CMF_90_UP) + convert_t_to_f(df, ColNames.ADAPTIVE_COMFORT) + convert_t_to_f(df, ColNames.ADAPTIVE_CMF_80_LOW) + convert_t_to_f(df, ColNames.ADAPTIVE_CMF_80_UP) + convert_t_to_f(df, ColNames.ADAPTIVE_CMF_90_LOW) + convert_t_to_f(df, ColNames.ADAPTIVE_CMF_90_UP) mapping_dict = json.loads(mapping_json) for key in json.loads(mapping_json): @@ -442,6 +442,19 @@ def convert_data(df, mapping_json): return json.dumps(mapping_dict) +def convert_t_to_f(df, name): + """Convert temperature from Celsius to Fahrenheit in-place for a given column. + + Args: + df: The DataFrame containing the temperature column. + name: Column name to convert. + + Returns: + None. The DataFrame is modified in-place. + """ + df[name] = df[name] * 1.8 + 32 + + def expand_to_hours(value, hours=8760): """Return a list with the input value repeated for a given number of hours. diff --git a/pages/psy-chart.py b/pages/psy-chart.py index c38e7b14..7cf0dfbf 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -11,7 +11,7 @@ from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.utils import get_max_min_value -from pages.lib.extract_df import temperature +from pages.lib.extract_df import convert_t_to_f from pages.lib.global_element_ids import ElementIds from pages.lib.global_column_names import ColNames from pages.lib.global_id_buttons import IdButtons @@ -357,7 +357,7 @@ def update_psych_chart( if si_ip == UnitSystem.IP: for j in range(len(dbt_list)): - temperature(dbt_list_convert, j) + convert_t_to_f(dbt_list_convert, j) fig.add_trace( go.Scatter( From 6570dc7bb302784bebe5fe3267afa0801422b0f9 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Thu, 4 Sep 2025 19:49:35 +1000 Subject: [PATCH 073/163] deps: restore dev dependencies in Pipfile --- Pipfile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Pipfile b/Pipfile index d6f89d9e..91288833 100644 --- a/Pipfile +++ b/Pipfile @@ -18,6 +18,11 @@ dash-iconify = "*" scipy = "==1.12.0" [dev-packages] +cleanpy = "*" +pytest = "*" +bump2version = "*" +black = "*" +ruff = "*" pre-commit = "*" [requires] From a6460a6a6007f6d80e919b3e348887e3df43c1c5 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Thu, 4 Sep 2025 20:12:23 +1000 Subject: [PATCH 074/163] refactor: add type annotations to utility functions --- pages/lib/extract_df.py | 12 +++++++++--- pages/lib/template_graphs.py | 4 +++- pages/lib/utils.py | 2 +- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 066b9dba..81cdda57 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -82,7 +82,13 @@ def get_location_info(lst, file_name): UTCI_LABELS = [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4] -def utci_calc(df: pd.DataFrame, t_air_col, t_rad_col, wind_col, rh_col=ColNames.RH): +def utci_calc( + df: pd.DataFrame, + t_air_col: str, + t_rad_col: str, + wind_col: str, + rh_col: str = ColNames.RH, +) -> pd.Series: """Call utci() using values from df columns.""" return utci(df[t_air_col], df[t_rad_col], df[wind_col], df[rh_col]) @@ -442,7 +448,7 @@ def convert_data(df, mapping_json): return json.dumps(mapping_dict) -def convert_t_to_f(df, name): +def convert_t_to_f(df: pd.DataFrame, name: str): """Convert temperature from Celsius to Fahrenheit in-place for a given column. Args: @@ -455,7 +461,7 @@ def convert_t_to_f(df, name): df[name] = df[name] * 1.8 + 32 -def expand_to_hours(value, hours=8760): +def expand_to_hours(value: any, hours: int = 8760) -> list[any]: """Return a list with the input value repeated for a given number of hours. Args: diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 216f0fac..f8e36418 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -838,7 +838,9 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): return fig -def time_filtering(df, start_time, end_time, time_col, target_col): +def time_filtering( + df: pd.DataFrame, start_time: int, end_time: int, time_col: str, target_col: str +) -> pd.DataFrame: """Mask values in the target column based on the given time range. Args: diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 8671ba77..539ca233 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -292,7 +292,7 @@ def dropdown(options=None, **kwargs): ) -def get_max_min_value(series, base=5): +def get_max_min_value(series: pd.Series, base: int = 5) -> tuple[int, int]: """Calculate rounded-up max and rounded-down min values based on a base step. Args: From eaa2eb33ed39737d87e2cdbd6212b556040e0459 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Fri, 5 Sep 2025 16:24:28 +1000 Subject: [PATCH 075/163] deps: fix dev dependencies in Pipfile.lock --- Pipfile.lock | 551 ++++++++++++++++++++++++++------------------------- 1 file changed, 277 insertions(+), 274 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index 3b8b17ea..15c208f9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4fe266b227936bffb73552e6657f642511470c74fa9f3236f6eb0b6c2e7b095c" + "sha256": "7214a1158f64483648ecff36a57b61b67020408e0f16770b77d7165a9d6f47e0" }, "pipfile-spec": 6, "requires": { @@ -16,44 +16,6 @@ ] }, "default": { - "ansi2html": { - "hashes": [ - "sha256:3453bf87535d37b827b05245faaa756dbab4ec3d69925e352b6319c3c955c0a5", - "sha256:dccb75aa95fb018e5d299be2b45f802952377abfdce0504c17a6ee6ef0a420c5" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==1.9.2" - }, - "black": { - "hashes": [ - "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", - "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", - "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", - "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", - "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", - "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", - "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", - "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", - "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", - "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", - "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", - "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", - "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", - "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", - "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", - "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", - "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", - "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", - "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", - "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", - "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", - "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==25.1.0" - }, "blinker": { "hashes": [ "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", @@ -62,15 +24,6 @@ "markers": "python_version >= '3.9'", "version": "==1.9.0" }, - "bump2version": { - "hashes": [ - "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", - "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" - ], - "index": "pypi", - "markers": "python_version >= '3.5'", - "version": "==1.0.1" - }, "cachelib": { "hashes": [ "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5", @@ -87,14 +40,6 @@ "markers": "python_version >= '3.7'", "version": "==2025.8.3" }, - "cfgv": { - "hashes": [ - "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", - "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" - ], - "markers": "python_version >= '3.8'", - "version": "==3.4.0" - }, "charset-normalizer": { "hashes": [ "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", @@ -180,15 +125,6 @@ "markers": "python_version >= '3.7'", "version": "==3.4.3" }, - "cleanpy": { - "hashes": [ - "sha256:9ddfa7ce80dd888b597a8b0bfeea3b69567839b6f41b775a4f76f46914d5170e", - "sha256:c60589d5da68527ca0c9151e28ed56fffae69df4ab6c9bfd8c1cf1d9e76a09b8" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==0.5.1" - }, "click": { "hashes": [ "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", @@ -197,6 +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", @@ -268,13 +212,6 @@ ], "version": "==0.22.3" }, - "distlib": { - "hashes": [ - "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", - "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d" - ], - "version": "==0.4.0" - }, "editorconfig": { "hashes": [ "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82", @@ -283,14 +220,6 @@ "markers": "python_version >= '3.9'", "version": "==0.17.1" }, - "filelock": { - "hashes": [ - "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", - "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d" - ], - "markers": "python_version >= '3.9'", - "version": "==3.19.1" - }, "flask": { "hashes": [ "sha256:09c347a92aa7ff4a8e7f3206795f30d826654baf38b873d0744cd571ca609efc", @@ -339,14 +268,6 @@ "markers": "python_version >= '3.9'", "version": "==3.14.0" }, - "identify": { - "hashes": [ - "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", - "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32" - ], - "markers": "python_version >= '3.9'", - "version": "==2.6.13" - }, "idna": { "hashes": [ "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", @@ -363,15 +284,6 @@ "markers": "python_version >= '3.9'", "version": "==8.7.0" }, - "iniconfig": { - "hashes": [ - "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", - "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.1.0" - }, "itsdangerous": { "hashes": [ "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", @@ -497,24 +409,6 @@ "markers": "python_version >= '3.7'", "version": "==9.1.0" }, - "mypy-extensions": { - "hashes": [ - "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", - "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==1.1.0" - }, - "narwhals": { - "hashes": [ - "sha256:235e61ca807bc21110ca36a4d53888ecc22c42dcdf50a7c886e10dde3fd7f38c", - "sha256:837457e36a2ba1710c881fb69e1f79ce44fb81728c92ac378f70892a53af8ddb" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==2.0.1" - }, "nest-asyncio": { "hashes": [ "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe", @@ -523,14 +417,6 @@ "markers": "python_version >= '3.5'", "version": "==1.6.0" }, - "nodeenv": { - "hashes": [ - "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", - "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" - ], - "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": "==1.9.1" - }, "numba": { "hashes": [ "sha256:34fba9406078bac7ab052efbf0d13939426c753ad72946baaa5bf9ae0ebb8dd2", @@ -645,24 +531,6 @@ "markers": "python_version >= '3.9'", "version": "==2.2.0" }, - "pathspec": { - "hashes": [ - "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", - "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==0.12.1" - }, - "platformdirs": { - "hashes": [ - "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", - "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==4.3.8" - }, "plotly": { "hashes": [ "sha256:23aa8ea2f4fb364a20d34ad38235524bd9d691bf5299e800bca608c31e8db8de", @@ -672,24 +540,6 @@ "markers": "python_version >= '3.6'", "version": "==5.18.0" }, - "pluggy": { - "hashes": [ - "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", - "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==1.6.0" - }, - "pre-commit": { - "hashes": [ - "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", - "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==4.3.0" - }, "pvlib": { "hashes": [ "sha256:ead96f47898befd7728ab0b61b9747231008e151ef78a26d5e41d0b6a95a3a9d", @@ -699,24 +549,6 @@ "markers": "python_version >= '3.6'", "version": "==0.9.1" }, - "pygments": { - "hashes": [ - "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", - "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.19.2" - }, - "pytest": { - "hashes": [ - "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", - "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c" - ], - "index": "pypi", - "markers": "python_version >= '3.9'", - "version": "==8.4.1" - }, "pythermalcomfort": { "hashes": [ "sha256:607995f6920a03911c7b9fddd06d819db1fa6e658d742b4ec8395a2c90707da5", @@ -731,7 +563,7 @@ "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9.0.post0" }, "pytz": { @@ -741,65 +573,6 @@ ], "version": "==2025.2" }, - "pyyaml": { - "hashes": [ - "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", - "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", - "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", - "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", - "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", - "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", - "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", - "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", - "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", - "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", - "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", - "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", - "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", - "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", - "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", - "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", - "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", - "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", - "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", - "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", - "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", - "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", - "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", - "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", - "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", - "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", - "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", - "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", - "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", - "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", - "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", - "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", - "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", - "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", - "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", - "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", - "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", - "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", - "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", - "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", - "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", - "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", - "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", - "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", - "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", - "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", - "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", - "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", - "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", - "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", - "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", - "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", - "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" - ], - "markers": "python_version >= '3.8'", - "version": "==6.0.2" - }, "requests": { "hashes": [ "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", @@ -817,31 +590,6 @@ "markers": "python_version >= '3.6'", "version": "==1.4.2" }, - "ruff": { - "hashes": [ - "sha256:06bfb01e1623bf7f59ea749a841da56f8f653d641bfd046edee32ede7ff6c606", - "sha256:1fc3193f238bc2d7968772c82831a4ff69252f673be371fb49663f0068b7ec71", - "sha256:2e1c2a3b8626339bb6369116e7030a4cf194ea48f49b64bb505732a7fce4f4e3", - "sha256:32dec41817623d388e645612ec70d5757a6d9c035f3744a52c7b195a57e03860", - "sha256:4000623300563c709458d0ce170c3d0d788c23a058912f28bbadc6f905d67afa", - "sha256:47ef751f722053a5df5fa48d412dbb54d41ab9b17875c6840a58ec63ff0c247c", - "sha256:5726f59b171111fa6a69d82aef48f00b56598b03a22f0f4170664ff4d8298efb", - "sha256:5d0bfe4e77fba61bf2ccadf8cf005d6133e3ce08793bbe870dd1c734f2699a3e", - "sha256:69ffe0e5f9b2cf2b8e289a3f8945b402a1b19eff24ec389f45f23c42a3dd6fb5", - "sha256:74e6f5c04c4dd4aba223f4fe6e7104f79e0eebf7d307e4f9b18c18362124bccd", - "sha256:76e4f31529899b8c434c3c1dede98c4483b89590e15fb49f2d46183801565303", - "sha256:789b7a03e72507c54fb3ba6209e4bb36517b90f1a3569ea17084e3fd295500fb", - "sha256:9c18f3d707ee9edf89da76131956aba1270c6348bfee8f6c647de841eac7194f", - "sha256:a07a5c8ffa2611a52732bdc67bf88e243abd84fe2d7f6daef3826b59abbfeda4", - "sha256:a828a5fc25a3efd3e1ff7b241fd392686c9386f20e5ac90aa9234a5faa12c423", - "sha256:c928f1b2ec59fb77dfdf70e0419408898b63998789cc98197e15f560b9e77f77", - "sha256:dfce05101dbd11833a0776716d5d1578641b7fddb537fe7fa956ab85d1769b69", - "sha256:e41df94a957d50083fd09b916d6e89e497246698c3f3d5c681c8b3e7b9bb4ac8" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==0.12.7" - }, "scipy": { "hashes": [ "sha256:196ebad3a4882081f62a5bf4aeb7326aa34b110e533aab23e4374fcccb0890dc", @@ -887,7 +635,7 @@ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.17.0" }, "tenacity": { @@ -900,11 +648,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", - "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" + "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", + "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" ], "markers": "python_version >= '3.9'", - "version": "==4.15.0" + "version": "==4.14.1" }, "tzdata": { "hashes": [ @@ -922,14 +670,6 @@ "markers": "python_version >= '3.9'", "version": "==2.5.0" }, - "virtualenv": { - "hashes": [ - "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", - "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a" - ], - "markers": "python_version >= '3.8'", - "version": "==20.34.0" - }, "werkzeug": { "hashes": [ "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17", @@ -946,5 +686,268 @@ "markers": "python_version >= '3.9'", "version": "==3.23.0" } + }, + "develop": { + "black": { + "hashes": [ + "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", + "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", + "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", + "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", + "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", + "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", + "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", + "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", + "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", + "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", + "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", + "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", + "sha256:a1ee0a0c330f7b5130ce0caed9936a904793576ef4d2b98c40835d6a65afa6a0", + "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", + "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", + "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", + "sha256:bacabb307dca5ebaf9c118d2d2f6903da0d62c9faa82bd21a33eecc319559355", + "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", + "sha256:d9e6827d563a2c820772b32ce8a42828dc6790f095f441beef18f96aa6f8294e", + "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", + "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", + "sha256:f3df5f1bf91d36002b0a75389ca8663510cf0531cca8aa5c1ef695b46d98655f" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==25.1.0" + }, + "bump2version": { + "hashes": [ + "sha256:37f927ea17cde7ae2d7baf832f8e80ce3777624554a653006c9144f8017fe410", + "sha256:762cb2bfad61f4ec8e2bdf452c7c267416f8c70dd9ecb1653fd0bbb01fa936e6" + ], + "index": "pypi", + "markers": "python_version >= '3.5'", + "version": "==1.0.1" + }, + "cfgv": { + "hashes": [ + "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", + "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560" + ], + "markers": "python_version >= '3.8'", + "version": "==3.4.0" + }, + "cleanpy": { + "hashes": [ + "sha256:9ddfa7ce80dd888b597a8b0bfeea3b69567839b6f41b775a4f76f46914d5170e", + "sha256:c60589d5da68527ca0c9151e28ed56fffae69df4ab6c9bfd8c1cf1d9e76a09b8" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==0.5.1" + }, + "click": { + "hashes": [ + "sha256:27c491cc05d968d271d5a1db13e3b5a184636d9d930f148c50b038f0d0646202", + "sha256:61a3265b914e850b85317d0b3109c7f8cd35a670f963866005d6ef1d5175a12b" + ], + "markers": "python_version >= '3.10'", + "version": "==8.2.1" + }, + "distlib": { + "hashes": [ + "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", + "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d" + ], + "version": "==0.4.0" + }, + "filelock": { + "hashes": [ + "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", + "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d" + ], + "markers": "python_version >= '3.9'", + "version": "==3.19.1" + }, + "identify": { + "hashes": [ + "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", + "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32" + ], + "markers": "python_version >= '3.9'", + "version": "==2.6.13" + }, + "iniconfig": { + "hashes": [ + "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", + "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760" + ], + "markers": "python_version >= '3.8'", + "version": "==2.1.0" + }, + "mypy-extensions": { + "hashes": [ + "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", + "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558" + ], + "markers": "python_version >= '3.8'", + "version": "==1.1.0" + }, + "nodeenv": { + "hashes": [ + "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", + "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9" + ], + "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": "==1.9.1" + }, + "packaging": { + "hashes": [ + "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", + "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f" + ], + "markers": "python_version >= '3.8'", + "version": "==25.0" + }, + "pathspec": { + "hashes": [ + "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", + "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712" + ], + "markers": "python_version >= '3.8'", + "version": "==0.12.1" + }, + "platformdirs": { + "hashes": [ + "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc", + "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4" + ], + "markers": "python_version >= '3.9'", + "version": "==4.3.8" + }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, + "pre-commit": { + "hashes": [ + "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", + "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==4.3.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", + "sha256:7c67fd69174877359ed9371ec3af8a3d2b04741818c51e5e99cc1742251fa93c" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==8.4.1" + }, + "pyyaml": { + "hashes": [ + "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff", + "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", + "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", + "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e", + "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", + "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", + "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", + "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", + "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", + "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", + "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a", + "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", + "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", + "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8", + "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", + "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19", + "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", + "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a", + "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", + "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", + "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", + "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631", + "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d", + "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", + "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", + "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", + "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", + "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", + "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", + "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706", + "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", + "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", + "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", + "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083", + "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", + "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", + "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", + "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f", + "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725", + "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", + "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", + "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", + "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", + "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", + "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5", + "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d", + "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290", + "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", + "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", + "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", + "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", + "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12", + "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4" + ], + "markers": "python_version >= '3.8'", + "version": "==6.0.2" + }, + "ruff": { + "hashes": [ + "sha256:07adb221c54b6bba24387911e5734357f042e5669fa5718920ee728aba3cbadc", + "sha256:17d5b6b0b3a25259b69ebcba87908496e6830e03acfb929ef9fd4c58675fa2ea", + "sha256:1b15599931a1a7a03c388b9c5df1bfa62be7ede6eb7ef753b272381f39c3d0ff", + "sha256:3d02faa2977fb6f3f32ddb7828e212b7dd499c59eb896ae6c03ea5c303575756", + "sha256:43f07a3ccfc62cdb4d3a3348bf0588358a66da756aa113e071b8ca8c3b9826af", + "sha256:5b15ea354c6ff0d7423814ba6d44be2807644d0c05e9ed60caca87e963e93f70", + "sha256:63c8c819739d86b96d500cce885956a1a48ab056bbcbc61b747ad494b2485089", + "sha256:6fb15b1977309741d7d098c8a3cb7a30bc112760a00fb6efb7abc85f00ba5908", + "sha256:72db7521860e246adbb43f6ef464dd2a532ef2ef1f5dd0d470455b8d9f1773e0", + "sha256:881465ed56ba4dd26a691954650de6ad389a2d1fdb130fe51ff18a25639fe4bb", + "sha256:9fc83e4e9751e6c13b5046d7162f205d0a7bac5840183c5beebf824b08a27340", + "sha256:a03242c1522b4e0885af63320ad754d53983c9599157ee33e77d748363c561ce", + "sha256:aed9d15f8c5755c0e74467731a007fcad41f19bcce41cd75f768bbd687f8535f", + "sha256:cc7a37bd2509974379d0115cc5608a1a4a6c4bff1b452ea69db83c8855d53f93", + "sha256:d596c2d0393c2502eaabfef723bd74ca35348a8dac4267d18a94910087807c53", + "sha256:f5cd34fabfdea3933ab85d72359f118035882a01bff15bd1d2b15261d85d5f66", + "sha256:f6be1d2ca0686c54564da8e7ee9e25f93bdd6868263805f8c0b8fc6a449db6d7", + "sha256:fbd94b2e3c623f659962934e52c2bea6fc6da11f667a427a368adaf3af2c866a", + "sha256:fcebc6c79fcae3f220d05585229463621f5dbf24d79fdc4936d9302e177cfa3e" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==0.12.9" + }, + "virtualenv": { + "hashes": [ + "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", + "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a" + ], + "markers": "python_version >= '3.8'", + "version": "==20.34.0" + } } } From 883e3c77859e82d3f023165332b03c7b4e92bcc0 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Sat, 6 Sep 2025 15:20:28 +1000 Subject: [PATCH 076/163] refactor: replace strings using Class ColNames in global_scheme.py --- pages/lib/global_column_names.py | 10 +- pages/lib/global_scheme.py | 729 +++++++++++++++---------------- 2 files changed, 362 insertions(+), 377 deletions(-) diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py index c2ef2135..7980b900 100644 --- a/pages/lib/global_column_names.py +++ b/pages/lib/global_column_names.py @@ -20,8 +20,13 @@ class ColNames: RH = "RH" # Relative Humidity HI_RH = "hiRH" # High Relative Humidity LO_RH = "loRH" # Low Relative Humidity - HR = "hr" + HR = "hr" # Absolute Humidity + EH = "h" # Enthalpy P_ATM = "p_atm" # Atmospheric Pressure + P_VAP = "p_vap" # Vapor partial Pressure + P_SAT = "p_sat" # Saturation Pressure + T_WB = "t_wb" # Wet Bulb Temperature + T_DP = "t_dp" # Dew Point Temperature # ==================== Radiation-related column ==================== EXTR_HOR_RAD = "extr_hor_rad" # Extraterrestrial Horizontal Radiation @@ -52,9 +57,11 @@ class ColNames: # SnowD = "SnowD" # Snow Depth # DaySSnow = "DaySSnow" # Daily Snow ELEVATION = "elevation" # Elevation + EQUATION_OF_TIME = "equation_of_time" # Equation of time APPARENT_ELEVATION = "apparent_elevation" # Apparent Elevation APPARENT_ZENITH = "apparent_zenith" # Apparent Zenith AZIMUTH = "azimuth" # Azimuth + ZENITH = "zenith" # Zenith MRT = "MRT" DELTA_MRT = "delta_mrt" UTCI_SUN_WIND = "utci_Sun_Wind" # Utci Sun Wind @@ -110,3 +117,4 @@ class ColNames: GEOMETRY_COORDINATES = "geometry.coordinates" PROP_ID = "prop_id" SITE_ELEVATION = "site_elevation" + NONE = "None" diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index 0029c946..0793276c 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -112,13 +112,21 @@ thermal_stress_label = "Thermal stress" mapping_dictionary = { - "None": {"name": "None"}, - "DOY": {"name": "Day of the year", "unit": "days", "range": [0, 365]}, - "day": {"name": "day", "range": [1, 31]}, - "month": {"name": "months", "unit": "months", "range": [1, 12]}, - "hour": { - "name": "Hour", - "color": [ + ColNames.NONE: {ColNames.NAME: "None"}, + ColNames.DOY: { + ColNames.NAME: "Day of the year", + ColNames.UNIT: "days", + ColNames.RANGE: [0, 365], + }, + ColNames.DAY: {ColNames.NAME: "day", ColNames.RANGE: [1, 31]}, + ColNames.MONTH: { + ColNames.NAME: "months", + ColNames.UNIT: "months", + ColNames.RANGE: [1, 12], + }, + ColNames.HOUR: { + ColNames.NAME: "Hour", + ColNames.COLOR: [ "#000000", "#355e7e", "#6b5c7b", @@ -129,51 +137,48 @@ "#c92a42", "#000000", ], - "unit": "h", - "range": [1, 24], + ColNames.UNIT: "h", + ColNames.RANGE: [1, 24], }, - "DBT": { - "name": "Dry bulb temperature", - "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], + ColNames.DBT: { + ColNames.NAME: "Dry bulb temperature", + ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], UnitSystem.SI: { - "unit": "°C", - "range": [-40, 50], + ColNames.UNIT: "°C", + ColNames.RANGE: [-40, 50], }, UnitSystem.IP: { - "unit": "°F", - "range": [-40, 122], + ColNames.UNIT: "°F", + ColNames.RANGE: [-40, 122], }, - "conversion_function": "temperature", }, - "DPT": { - "name": "Dew point temperature", - "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], + ColNames.DPT: { + ColNames.NAME: "Dew point temperature", + ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], UnitSystem.SI: { - "unit": "°C", - "range": [-50, 35], + ColNames.UNIT: "°C", + ColNames.RANGE: [-50, 35], }, UnitSystem.IP: { - "unit": "°F", - "range": [-58, 95], + ColNames.UNIT: "°F", + ColNames.RANGE: [-58, 95], }, - "conversion_function": "temperature", }, - "RH": { - "name": "Relative humidity", - "color": ["#ffe600", "#00c8ff", "#0000ff"], + ColNames.RH: { + ColNames.NAME: "Relative humidity", + ColNames.COLOR: ["#ffe600", "#00c8ff", "#0000ff"], UnitSystem.SI: { - "unit": "%", - "range": [0, 100], + ColNames.UNIT: "%", + ColNames.RANGE: [0, 100], }, UnitSystem.IP: { - "unit": "%", - "range": [0, 100], + ColNames.UNIT: "%", + ColNames.RANGE: [0, 100], }, - "conversion_function": None, }, - "p_atm": { - "name": "Atmospheric pressure", - "color": [ + ColNames.P_ATM: { + ColNames.NAME: "Atmospheric pressure", + ColNames.COLOR: [ "#ffffff", "#b2f2ff", "#33ddff", @@ -186,18 +191,17 @@ "#ffaa00", ], UnitSystem.SI: { - "unit": "Pa", - "range": [95000, 105000], + ColNames.UNIT: "Pa", + ColNames.RANGE: [95000, 105000], }, UnitSystem.IP: { - "unit": "Psi", - "range": [95000 * 0.000145038, 1050000.000145038], + ColNames.UNIT: "Psi", + ColNames.RANGE: [95000 * 0.000145038, 1050000.000145038], }, - "conversion_function": "pressure", }, - "extr_hor_rad": { - "name": "Extraterrestrial horizontal irradiation", - "color": [ + ColNames.EXTR_HOR_RAD: { + ColNames.NAME: "Extraterrestrial horizontal irradiation", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -207,18 +211,17 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "Wh/m2", - "range": [0, 1200], + ColNames.UNIT: "Wh/m2", + ColNames.RANGE: [0, 1200], }, UnitSystem.IP: { - "unit": "Btu/ft2", - "range": [0, 1200 * 0.3169983306], + ColNames.UNIT: "Btu/ft2", + ColNames.RANGE: [0, 1200 * 0.3169983306], }, - "conversion_function": "irradiation", }, - "hor_ir_rad": { - "name": "Horizontal infrared radiation", - "color": [ + ColNames.HOR_IR_RAD: { + ColNames.NAME: "Horizontal infrared radiation", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -228,18 +231,17 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "Wh/m2", - "range": [0, 500], + ColNames.UNIT: "Wh/m2", + ColNames.RANGE: [0, 500], }, UnitSystem.IP: { - "unit": "Btu/ft2", - "range": [0, 500 * 0.3169983306], + ColNames.UNIT: "Btu/ft2", + ColNames.RANGE: [0, 500 * 0.3169983306], }, - "conversion_function": "irradiation", }, - "glob_hor_rad": { - "name": "Global horizontal radiation", - "color": [ + ColNames.GLOB_HOR_RAD: { + ColNames.NAME: "Global horizontal radiation", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -249,18 +251,17 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "Wh/m2", - "range": [0, 1200], + ColNames.UNIT: "Wh/m2", + ColNames.RANGE: [0, 1200], }, UnitSystem.IP: { - "unit": "Btu/ft2", - "range": [0, 1200 * 0.3169983306], + ColNames.UNIT: "Btu/ft2", + ColNames.RANGE: [0, 1200 * 0.3169983306], }, - "conversion_function": "irradiation", }, - "dir_nor_rad": { - "name": "Direct normal radiation", - "color": [ + ColNames.DIR_NOR_RAD: { + ColNames.NAME: "Direct normal radiation", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -270,18 +271,17 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "Wh/m2", - "range": [0, 1200], + ColNames.UNIT: "Wh/m2", + ColNames.RANGE: [0, 1200], }, UnitSystem.IP: { - "unit": "Btu/ft2", - "range": [0, 1200 * 0.3169983306], + ColNames.UNIT: "Btu/ft2", + ColNames.RANGE: [0, 1200 * 0.3169983306], }, - "conversion_function": "irradiation", }, - "dif_hor_rad": { - "name": "Diffuse horizontal radiation", - "color": [ + ColNames.DIF_HOR_RAD: { + ColNames.NAME: "Diffuse horizontal radiation", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -291,83 +291,84 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "Wh/m2", - "range": [0, 1200], + ColNames.UNIT: "Wh/m2", + ColNames.RANGE: [0, 1200], }, UnitSystem.IP: { - "unit": "Btu/ft2", - "range": [0, 1200 * 0.3169983306], + ColNames.UNIT: "Btu/ft2", + ColNames.RANGE: [0, 1200 * 0.3169983306], }, - "conversion_function": "irradiation", }, - "glob_hor_ill": { - "name": "Global horizontal illuminance", - "color": ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], + ColNames.GLOB_HOR_ILL: { + ColNames.NAME: "Global horizontal illuminance", + ColNames.COLOR: ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], UnitSystem.SI: { - "unit": "lux", - "range": [0, 120000], + ColNames.UNIT: "lux", + ColNames.RANGE: [0, 120000], }, UnitSystem.IP: { - "unit": "fc", - "range": [0, 120000 * 0.0929], + ColNames.UNIT: "fc", + ColNames.RANGE: [0, 120000 * 0.0929], }, - "conversion_function": "illuminance", }, - "dir_nor_ill": { - "name": "Direct normal illuminance", - "color": ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], + ColNames.DIR_NOR_ILL: { + ColNames.NAME: "Direct normal illuminance", + ColNames.COLOR: ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], UnitSystem.SI: { - "unit": "lux", - "range": [0, 120000], + ColNames.UNIT: "lux", + ColNames.RANGE: [0, 120000], }, UnitSystem.IP: { - "unit": "fc", - "range": [0, 120000 * 0.0929], + ColNames.UNIT: "fc", + ColNames.RANGE: [0, 120000 * 0.0929], }, - "conversion_function": "illuminance", }, - "dif_hor_ill": { - "name": "Diffuse horizontal illuminance", - "color": ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], + ColNames.DIF_HOR_ILL: { + ColNames.NAME: "Diffuse horizontal illuminance", + ColNames.COLOR: ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], UnitSystem.SI: { - "unit": "lux", - "range": [0, 120000], + ColNames.UNIT: "lux", + ColNames.RANGE: [0, 120000], }, UnitSystem.IP: { - "unit": "fc", - "range": [0, 120000 * 0.0929], + ColNames.UNIT: "fc", + ColNames.RANGE: [0, 120000 * 0.0929], }, - "conversion_function": "illuminance", }, - "Zlumi": { - "name": "Zenith luminance", - "color": ["#730a8c", "#0d0db3", "#0f85be", "#0f85be", "#b11421", "#fdf130"], + ColNames.ZLUMI: { + ColNames.NAME: "Zenith luminance", + ColNames.COLOR: [ + "#730a8c", + "#0d0db3", + "#0f85be", + "#0f85be", + "#b11421", + "#fdf130", + ], UnitSystem.SI: { - "unit": "cd/m2", - "range": [0, 60000], + ColNames.UNIT: "cd/m2", + ColNames.RANGE: [0, 60000], }, UnitSystem.IP: { - "unit": "cd/ft2", - "range": [0, 60000 * 0.0929], + ColNames.UNIT: "cd/ft2", + ColNames.RANGE: [0, 60000 * 0.0929], }, - "conversion_function": "zenith_illuminance", }, - "wind_dir": { - "name": "Wind direction", - "color": ["#0072dd", "#00c420", "#eded00", "#be00d5", "#0072dd"], + ColNames.WIND_DIR: { + ColNames.NAME: "Wind direction", + ColNames.COLOR: ["#0072dd", "#00c420", "#eded00", "#be00d5", "#0072dd"], UnitSystem.SI: { - "unit": "°deg", - "range": [0, 360], + ColNames.UNIT: "°deg", + ColNames.RANGE: [0, 360], }, UnitSystem.IP: { - "unit": "°deg", - "range": [0, 360], + ColNames.UNIT: "°deg", + ColNames.RANGE: [0, 360], }, - "conversion_function": None, }, ColNames.WIND_SPEED: { - "name": "Wind speed", - "color": [ + ColNames.NAME: "Wind speed", + ColNames.COLOR: [ "#D3D3D3", "#b2f2ff", "#33ddff", @@ -380,57 +381,53 @@ "#ffaa00", ], UnitSystem.SI: { - "unit": "m/s", - "range": [0, 20], + ColNames.UNIT: "m/s", + ColNames.RANGE: [0, 20], }, UnitSystem.IP: { - "unit": "fpm", - "range": [0, 20 * 196.85039370078738], + ColNames.UNIT: "fpm", + ColNames.RANGE: [0, 20 * 196.85039370078738], }, - "conversion_function": "speed", }, - "tot_sky_cover": { - "name": "Total sky cover", - "color": cloud_colors, + ColNames.TOT_SKY_COVER: { + ColNames.NAME: "Total sky cover", + ColNames.COLOR: cloud_colors, UnitSystem.SI: { - "unit": "tenths", - "range": [0, 10], + ColNames.UNIT: "tenths", + ColNames.RANGE: [0, 10], }, UnitSystem.IP: { - "unit": "tenths", - "range": [0, 10], + ColNames.UNIT: "tenths", + ColNames.RANGE: [0, 10], }, - "conversion_function": None, }, - "Oskycover": { - "name": "Opaque sky cover", - "color": cloud_colors, + ColNames.OSKYCOVER: { + ColNames.NAME: "Opaque sky cover", + ColNames.COLOR: cloud_colors, UnitSystem.SI: { - "unit": "tenths", - "range": [0, 10], + ColNames.UNIT: "tenths", + ColNames.RANGE: [0, 10], }, UnitSystem.IP: { - "unit": "tenths", - "range": [0, 10], + ColNames.UNIT: "tenths", + ColNames.RANGE: [0, 10], }, - "conversion_function": None, }, - "Vis": { - "name": "Visibility", - "color": cloud_colors, + ColNames.VIS: { + ColNames.NAME: "Visibility", + ColNames.COLOR: cloud_colors, UnitSystem.SI: { - "unit": "km", - "range": [0, 100], + ColNames.UNIT: "km", + ColNames.RANGE: [0, 100], }, UnitSystem.IP: { - "unit": "miles", - "range": [0, 100 * 0.6215], + ColNames.UNIT: "miles", + ColNames.RANGE: [0, 100 * 0.6215], }, - "conversion_function": "visibility", }, - "apparent_zenith": { - "name": "Apparent zenith", - "color": [ + ColNames.APPARENT_ZENITH: { + ColNames.NAME: "Apparent zenith", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -440,18 +437,17 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "°deg", - "range": [0, 180], + ColNames.UNIT: "°deg", + ColNames.RANGE: [0, 180], }, UnitSystem.IP: { - "unit": "°deg", - "range": [0, 180], + ColNames.UNIT: "°deg", + ColNames.RANGE: [0, 180], }, - "conversion_function": None, }, - "zenith": { - "name": "Zenith", - "color": [ + ColNames.ZENITH: { + ColNames.NAME: "Zenith", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -461,18 +457,17 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "°deg", - "range": [0, 180], + ColNames.UNIT: "°deg", + ColNames.RANGE: [0, 180], }, UnitSystem.IP: { - "unit": "°deg", - "range": [0, 180], + ColNames.UNIT: "°deg", + ColNames.RANGE: [0, 180], }, - "conversion_function": None, }, - "apparent_elevation": { - "name": "Apparent elevation", - "color": [ + ColNames.APPARENT_ELEVATION: { + ColNames.NAME: "Apparent elevation", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -482,18 +477,17 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "°deg", - "range": [-90, 90], + ColNames.UNIT: "°deg", + ColNames.RANGE: [-90, 90], }, UnitSystem.IP: { - "unit": "°deg", - "range": [-90, 90], + ColNames.UNIT: "°deg", + ColNames.RANGE: [-90, 90], }, - "conversion_function": None, }, - "elevation": { - "name": "Elevation", - "color": [ + ColNames.ELEVATION: { + ColNames.NAME: "Elevation", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -503,18 +497,17 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "°deg", - "range": [-90, 90], + ColNames.UNIT: "°deg", + ColNames.RANGE: [-90, 90], }, UnitSystem.IP: { - "unit": "°deg", - "range": [-90, 90], + ColNames.UNIT: "°deg", + ColNames.RANGE: [-90, 90], }, - "conversion_function": None, }, - "azimuth": { - "name": "Azimuth", - "color": [ + ColNames.AZIMUTH: { + ColNames.NAME: "Azimuth", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -524,18 +517,17 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "°deg", - "range": [0, 360], + ColNames.UNIT: "°deg", + ColNames.RANGE: [0, 360], }, UnitSystem.IP: { - "unit": "°deg", - "range": [0, 360], + ColNames.UNIT: "°deg", + ColNames.RANGE: [0, 360], }, - "conversion_function": None, }, - "equation_of_time": { - "name": "Equation of time", - "color": [ + ColNames.EQUATION_OF_TIME: { + ColNames.NAME: "Equation of time", + ColNames.COLOR: [ "#293a59", "#960c2c", "#ff0000", @@ -545,70 +537,65 @@ "#ffffff", ], UnitSystem.SI: { - "unit": "°deg", - "range": [-20, 20], + ColNames.UNIT: "°deg", + ColNames.RANGE: [-20, 20], }, UnitSystem.IP: { - "unit": "°deg", - "range": [-20, 20], + ColNames.UNIT: "°deg", + ColNames.RANGE: [-20, 20], }, - "conversion_function": None, }, - "utci_Sun_Wind": { - "name": "UTCI: Sun & Wind", - "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], + ColNames.UTCI_SUN_WIND: { + ColNames.NAME: "UTCI: Sun & Wind", + ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], UnitSystem.SI: { - "unit": "°C", - "range": [-70, 70], + ColNames.UNIT: "°C", + ColNames.RANGE: [-70, 70], }, UnitSystem.IP: { - "unit": "°F", - "range": [-94, 158], + ColNames.UNIT: "°F", + ColNames.RANGE: [-94, 158], }, - "conversion_function": "temperature", }, - "utci_noSun_Wind": { - "name": "UTCI: no Sun & Wind", - "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], + ColNames.UTCI_NO_SUN_WIND: { + ColNames.NAME: "UTCI: no Sun & Wind", + ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], UnitSystem.SI: { - "unit": "°C", - "range": [-70, 70], + ColNames.UNIT: "°C", + ColNames.RANGE: [-70, 70], }, UnitSystem.IP: { - "unit": "°F", - "range": [-94, 158], + ColNames.UNIT: "°F", + ColNames.RANGE: [-94, 158], }, - "conversion_function": "temperature", }, - "utci_Sun_noWind": { - "name": "UTCI: Sun & no Wind", - "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], + ColNames.UTCI_SUN_NO_WIND: { + ColNames.NAME: "UTCI: Sun & no Wind", + ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], UnitSystem.SI: { - "unit": "°C", - "range": [-70, 70], + ColNames.UNIT: "°C", + ColNames.RANGE: [-70, 70], }, UnitSystem.IP: { - "unit": "°F", - "range": [-94, 158], + ColNames.UNIT: "°F", + ColNames.RANGE: [-94, 158], }, - "conversion_function": "temperature", }, - "utci_noSun_noWind": { - "name": "UTCI: no Sun & no Wind", - "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], + ColNames.UTCI_NO_SUN_NO_WIND: { + ColNames.NAME: "UTCI: no Sun & no Wind", + ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], UnitSystem.SI: { - "unit": "°C", - "range": [-70, 70], + ColNames.UNIT: "°C", + ColNames.RANGE: [-70, 70], }, UnitSystem.IP: { - "unit": "°F", - "range": [-94, 158], + ColNames.UNIT: "°F", + ColNames.RANGE: [-94, 158], }, - "conversion_function": "temperature", }, - "utci_Sun_Wind_categories": { - "name": "UTCI: Sun & Wind : categories", - "color": [ + ColNames.UTCI_SUN_WIND_CATEGORIES: { + ColNames.NAME: "UTCI: Sun & Wind : categories", + ColNames.COLOR: [ [0, "#2B2977"], [0.0555, "#2B2977"], [0.0555, "#38429B"], @@ -631,18 +618,17 @@ [1.0, "#751613"], ], UnitSystem.SI: { - "unit": thermal_stress_label, - "range": [-5, 4], + ColNames.UNIT: thermal_stress_label, + ColNames.RANGE: [-5, 4], }, UnitSystem.IP: { - "unit": thermal_stress_label, - "range": [-5, 4], + ColNames.UNIT: thermal_stress_label, + ColNames.RANGE: [-5, 4], }, - "conversion_function": None, }, - "utci_noSun_Wind_categories": { - "name": "UTCI: no Sun & Wind : categories", - "color": [ + ColNames.UTCI_NOSUN_WIND_CATEGORIES: { + ColNames.NAME: "UTCI: no Sun & Wind : categories", + ColNames.COLOR: [ [0, "#2B2977"], [0.0555, "#2B2977"], [0.0555, "#38429B"], @@ -665,18 +651,17 @@ [1.0, "#751613"], ], UnitSystem.SI: { - "unit": thermal_stress_label, - "range": [-5, 4], + ColNames.UNIT: thermal_stress_label, + ColNames.RANGE: [-5, 4], }, UnitSystem.IP: { - "unit": thermal_stress_label, - "range": [-5, 4], + ColNames.UNIT: thermal_stress_label, + ColNames.RANGE: [-5, 4], }, - "conversion_function": None, }, - "utci_Sun_noWind_categories": { - "name": "UTCI: Sun & no Wind : categories", - "color": [ + ColNames.UTCI_SUN_NOWIND_CATEGORIES: { + ColNames.NAME: "UTCI: Sun & no Wind : categories", + ColNames.COLOR: [ [0, "#2B2977"], [0.0555, "#2B2977"], [0.0555, "#38429B"], @@ -699,18 +684,17 @@ [1.0, "#751613"], ], UnitSystem.SI: { - "unit": thermal_stress_label, - "range": [-5, 4], + ColNames.UNIT: thermal_stress_label, + ColNames.RANGE: [-5, 4], }, UnitSystem.IP: { - "unit": thermal_stress_label, - "range": [-5, 4], + ColNames.UNIT: thermal_stress_label, + ColNames.RANGE: [-5, 4], }, - "conversion_function": None, }, - "utci_noSun_noWind_categories": { - "name": "UTCI: no Sun & no Wind : categories", - "color": [ + ColNames.UTCI_NOSUN_NOWIND_CATEGORIES: { + ColNames.NAME: "UTCI: no Sun & no Wind : categories", + ColNames.COLOR: [ [0, "#2B2977"], [0.0555, "#2B2977"], [0.0555, "#38429B"], @@ -733,171 +717,164 @@ [1.0, "#751613"], ], UnitSystem.SI: { - "unit": thermal_stress_label, - "range": [-5, 4], + ColNames.UNIT: thermal_stress_label, + ColNames.RANGE: [-5, 4], }, UnitSystem.IP: { - "unit": thermal_stress_label, - "range": [-5, 4], + ColNames.UNIT: thermal_stress_label, + ColNames.RANGE: [-5, 4], }, - "conversion_function": None, }, - "p_vap": { - "name": "Vapor partial pressure", - "color": ["#ffe600", "#00c8ff", "#0000ff"], + ColNames.P_VAP: { + ColNames.NAME: "Vapor partial pressure", + ColNames.COLOR: ["#ffe600", "#00c8ff", "#0000ff"], UnitSystem.SI: { - "unit": "Pa", - "range": [0, 5000], + ColNames.UNIT: "Pa", + ColNames.RANGE: [0, 5000], }, UnitSystem.IP: { - "unit": "Psi", - "range": [0, 5000 * 0.000145038], + ColNames.UNIT: "Psi", + ColNames.RANGE: [0, 5000 * 0.000145038], }, - "conversion_function": "pressure", }, - "p_sat": { - "name": "Saturation pressure", + ColNames.P_SAT: { + ColNames.NAME: "Saturation pressure", UnitSystem.SI: { - "unit": "Pa", - "range": [0, 5000], + ColNames.UNIT: "Pa", + ColNames.RANGE: [0, 5000], }, UnitSystem.IP: { - "unit": "Psi", - "range": [0, 5000 * 0.000145038], + ColNames.UNIT: "Psi", + ColNames.RANGE: [0, 5000 * 0.000145038], }, - "conversion_function": "pressure", }, - "hr": { - "name": "Absolute humidity", - "color": ["#ffe600", "#00c8ff", "#0000ff"], + ColNames.HR: { + ColNames.NAME: "Absolute humidity", + ColNames.COLOR: ["#ffe600", "#00c8ff", "#0000ff"], UnitSystem.SI: { - "unit": "g water/kg dry air", - "range": [0, 0.03 * 1000], + ColNames.UNIT: "g water/kg dry air", + ColNames.RANGE: [0, 0.03 * 1000], }, UnitSystem.IP: { - "unit": "lb water/klb dry air", - "range": [0, 0.03 * 1000], + ColNames.UNIT: "lb water/klb dry air", + ColNames.RANGE: [0, 0.03 * 1000], }, - "conversion_function": None, }, - "t_wb": { - "name": "Wet bulb temperature", - "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], + ColNames.T_WB: { + ColNames.NAME: "Wet bulb temperature", + ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], UnitSystem.SI: { - "unit": "°C", - "range": [-40, 50], + ColNames.UNIT: "°C", + ColNames.RANGE: [-40, 50], }, UnitSystem.IP: { - "unit": "°F", - "range": [-40, 122], + ColNames.UNIT: "°F", + ColNames.RANGE: [-40, 122], }, - "conversion_function": "temperature", }, - "t_dp": { - "name": "Dew point temperature", - "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], + ColNames.T_DP: { + ColNames.NAME: "Dew point temperature", + ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], UnitSystem.SI: { - "unit": "°C", - "range": [-40, 50], + ColNames.UNIT: "°C", + ColNames.RANGE: [-40, 50], }, UnitSystem.IP: { - "unit": "°F", - "range": [-40, 122], + ColNames.UNIT: "°F", + ColNames.RANGE: [-40, 122], }, - "conversion_function": "temperature", }, - "h": { - "name": "Enthalpy", - "color": ["#00b3ff", "#000082", "#ff0000", "#ffff00"], + ColNames.EH: { + ColNames.NAME: "Enthalpy", + ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], UnitSystem.SI: { - "unit": "J/kg dry air", - "range": [0, 110000], + ColNames.UNIT: "J/kg dry air", + ColNames.RANGE: [0, 110000], }, UnitSystem.IP: { - "unit": "Btu/lb dry air", - "range": [0, 110000 * 0.000429923], + ColNames.UNIT: "Btu/lb dry air", + ColNames.RANGE: [0, 110000 * 0.000429923], }, - "conversion_function": "enthalpy", }, } # Dropdown Names variables_sun_cloud_tab_dropdown = [ - "None", - "t_wb", - "DPT", - "DBT", - "RH", - "p_vap", - "hr", - "extr_hor_rad", - "hor_ir_rad", - "glob_hor_rad", - "dir_nor_rad", - "dif_hor_rad", - "glob_hor_ill", - "dir_nor_ill", - "dif_hor_ill", - "Zlumi", - "wind_dir", + ColNames.NONE, + ColNames.T_WB, + ColNames.DPT, + ColNames.DBT, + ColNames.RH, + ColNames.P_VAP, + ColNames.HR, + ColNames.EXTR_HOR_RAD, + ColNames.HOR_IR_RAD, + ColNames.GLOB_HOR_RAD, + ColNames.DIR_NOR_RAD, + ColNames.DIF_HOR_RAD, + ColNames.GLOB_HOR_ILL, + ColNames.DIR_NOR_ILL, + ColNames.DIF_HOR_ILL, + ColNames.ZLUMI, + ColNames.WIND_DIR, ColNames.WIND_SPEED, - "tot_sky_cover", - "Oskycover", - "Vis", + ColNames.TOT_SKY_COVER, + ColNames.OSKYCOVER, + ColNames.VIS, ] variables_dropdown = [ - "t_wb", - "DPT", - "DBT", - "RH", - "p_vap", - "hr", - "extr_hor_rad", - "hor_ir_rad", - "glob_hor_rad", - "dir_nor_rad", - "dif_hor_rad", - "glob_hor_ill", - "dir_nor_ill", - "dif_hor_ill", - "Zlumi", - "wind_dir", + ColNames.T_WB, + ColNames.DPT, + ColNames.DBT, + ColNames.RH, + ColNames.P_VAP, + ColNames.HR, + ColNames.EXTR_HOR_RAD, + ColNames.HOR_IR_RAD, + ColNames.GLOB_HOR_RAD, + ColNames.DIR_NOR_RAD, + ColNames.DIF_HOR_RAD, + ColNames.GLOB_HOR_ILL, + ColNames.DIR_NOR_ILL, + ColNames.DIF_HOR_ILL, + ColNames.ZLUMI, + ColNames.WIND_DIR, ColNames.WIND_SPEED, - "tot_sky_cover", - "Oskycover", - "Vis", + ColNames.TOT_SKY_COVER, + ColNames.OSKYCOVER, + ColNames.VIS, ] variables_more_variables_dropdown = [ - "utci_Sun_Wind", - "utci_noSun_Wind", - "utci_Sun_noWind", - "utci_noSun_noWind", - "utci_Sun_Wind_categories", - "utci_noSun_Wind_categories", - "utci_Sun_noWind_categories", - "utci_noSun_noWind_categories", - "t_dp", - "elevation", - "azimuth", - "p_sat", + ColNames.UTCI_SUN_WIND, + ColNames.UTCI_NO_SUN_WIND, + ColNames.UTCI_SUN_NO_WIND, + ColNames.UTCI_NO_SUN_NO_WIND, + ColNames.UTCI_SUN_WIND_CATEGORIES, + ColNames.UTCI_NOSUN_WIND_CATEGORIES, + ColNames.UTCI_SUN_NOWIND_CATEGORIES, + ColNames.UTCI_NOSUN_NOWIND_CATEGORIES, + ColNames.T_DP, + ColNames.ELEVATION, + ColNames.AZIMUTH, + ColNames.P_SAT, ] variables_sun_cloud_tab_explore_dropdown = [ - "extr_hor_rad", - "hor_ir_rad", - "glob_hor_rad", - "dir_nor_rad", - "dif_hor_rad", - "glob_hor_ill", - "dir_nor_ill", - "dif_hor_ill", - "Zlumi", - "Oskycover", + ColNames.EXTR_HOR_RAD, + ColNames.HOR_IR_RAD, + ColNames.GLOB_HOR_RAD, + ColNames.DIR_NOR_RAD, + ColNames.DIF_HOR_RAD, + ColNames.GLOB_HOR_ILL, + ColNames.DIR_NOR_ILL, + ColNames.DIF_HOR_ILL, + ColNames.ZLUMI, + ColNames.OSKYCOVER, ] variables_outdoor_dropdown = [ - "utci_Sun_Wind", - "utci_Sun_noWind", - "utci_noSun_Wind", - "utci_noSun_noWind", + ColNames.UTCI_SUN_WIND, + ColNames.UTCI_SUN_NO_WIND, + ColNames.UTCI_NO_SUN_WIND, + ColNames.UTCI_NO_SUN_NO_WIND, ] sun_cloud_tab_dropdown_names = { From 4f6421ea8c2df8e9cf9c400902a15118386726ed Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Sat, 6 Sep 2025 15:21:04 +1000 Subject: [PATCH 077/163] refactor: remove globals-based conversion map and implement convert_SI_to_IP() --- pages/lib/extract_df.py | 97 ++++++++++++++++++++--------------------- pages/psy-chart.py | 4 +- 2 files changed, 49 insertions(+), 52 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 81cdda57..bd0829a7 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -399,68 +399,65 @@ def create_df(lst, file_name): # convert function -def temperature(df, name): - df[name] = df[name] * 1.8 + 32 - - -def pressure(df, name): - df[name] = df[name] * 0.000145038 - - -def irradiation(df, name): - df[name] = df[name] * 0.3169983306 - - -def illuminance(df, name): - df[name] = df[name] * 0.0929 - - -def zenith_illuminance(df, name): - df[name] = df[name] * 0.0929 - - -def speed(df, name): - df[name] = df[name] * 196.85039370078738 - - -def visibility(df, name): - df[name] = df[name] * 0.6215 - - -def enthalpy(df, name): - df[name] = df[name] * 0.0004 +def convert_SI_to_IP(df: pd.DataFrame, name: str) -> None: + """Convert SI to IP based on column name.""" + match name: + case ( + ColNames.DBT + | ColNames.DPT + | ColNames.T_WB + | ColNames.T_DP + | ColNames.UTCI_SUN_WIND + | ColNames.UTCI_NO_SUN_WIND + | ColNames.UTCI_SUN_NO_WIND + | ColNames.UTCI_NO_SUN_NO_WIND + ): + df[name] = df[name] * 1.8 + 32 + + case ColNames.P_ATM | ColNames.P_VAP | ColNames.P_SAT: + df[name] = df[name] * 0.000145038 + + case ( + ColNames.EXTR_HOR_RAD + | ColNames.HOR_IR_RAD + | ColNames.GLOB_HOR_RAD + | ColNames.DIR_NOR_RAD + | ColNames.DIF_HOR_RAD + ): + df[name] = df[name] * 0.3169983306 + + case ColNames.GLOB_HOR_ILL | ColNames.DIR_NOR_ILL | ColNames.DIF_HOR_ILL: + df[name] = df[name] * 0.0929 + + case ColNames.ZLUMI: + df[name] = df[name] * 0.0929 + + case ColNames.WIND_SPEED: + df[name] = df[name] * 196.85039370078738 + + case ColNames.VIS: + df[name] = df[name] * 0.6215 + + case ColNames.EH: + df[name] = df[name] * 0.000429923 def convert_data(df, mapping_json): - convert_t_to_f(df, ColNames.ADAPTIVE_COMFORT) - convert_t_to_f(df, ColNames.ADAPTIVE_CMF_80_LOW) - convert_t_to_f(df, ColNames.ADAPTIVE_CMF_80_UP) - convert_t_to_f(df, ColNames.ADAPTIVE_CMF_90_LOW) - convert_t_to_f(df, ColNames.ADAPTIVE_CMF_90_UP) + convert_SI_to_IP(df, ColNames.ADAPTIVE_COMFORT) + convert_SI_to_IP(df, ColNames.ADAPTIVE_CMF_80_LOW) + convert_SI_to_IP(df, ColNames.ADAPTIVE_CMF_80_UP) + convert_SI_to_IP(df, ColNames.ADAPTIVE_CMF_90_LOW) + convert_SI_to_IP(df, ColNames.ADAPTIVE_CMF_90_UP) mapping_dict = json.loads(mapping_json) for key in json.loads(mapping_json): if ColNames.CONVERSION_FUNCTION in mapping_dict[key]: conversion_function_name = mapping_dict[key][ColNames.CONVERSION_FUNCTION] if conversion_function_name is not None: - conversion_function = globals()[conversion_function_name] - conversion_function(df, key) + convert_SI_to_IP(df, key) return json.dumps(mapping_dict) -def convert_t_to_f(df: pd.DataFrame, name: str): - """Convert temperature from Celsius to Fahrenheit in-place for a given column. - - Args: - df: The DataFrame containing the temperature column. - name: Column name to convert. - - Returns: - None. The DataFrame is modified in-place. - """ - df[name] = df[name] * 1.8 + 32 - - def expand_to_hours(value: any, hours: int = 8760) -> list[any]: """Return a list with the input value repeated for a given number of hours. diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 7cf0dfbf..bf967751 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -11,7 +11,7 @@ from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.utils import get_max_min_value -from pages.lib.extract_df import convert_t_to_f +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 @@ -357,7 +357,7 @@ def update_psych_chart( if si_ip == UnitSystem.IP: for j in range(len(dbt_list)): - convert_t_to_f(dbt_list_convert, j) + convert_SI_to_IP(dbt_list_convert, j) fig.add_trace( go.Scatter( From bf149b0edc38034e4b726c02a2de665ef9e94f4a Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Mon, 8 Sep 2025 22:35:33 +1000 Subject: [PATCH 078/163] refactor: apply CodeRabbit suggestions to simplify convert_data logic --- pages/lib/extract_df.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index bd0829a7..6f5f8fd1 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -401,6 +401,8 @@ def create_df(lst, file_name): # convert function def convert_SI_to_IP(df: pd.DataFrame, name: str) -> None: """Convert SI to IP based on column name.""" + if name not in df.columns: + return match name: case ( ColNames.DBT @@ -411,6 +413,11 @@ def convert_SI_to_IP(df: pd.DataFrame, name: str) -> None: | ColNames.UTCI_NO_SUN_WIND | ColNames.UTCI_SUN_NO_WIND | ColNames.UTCI_NO_SUN_NO_WIND + | ColNames.ADAPTIVE_COMFORT + | ColNames.ADAPTIVE_CMF_80_LOW + | ColNames.ADAPTIVE_CMF_80_UP + | ColNames.ADAPTIVE_CMF_90_LOW + | ColNames.ADAPTIVE_CMF_90_UP ): df[name] = df[name] * 1.8 + 32 @@ -443,19 +450,12 @@ def convert_SI_to_IP(df: pd.DataFrame, name: str) -> None: def convert_data(df, mapping_json): - convert_SI_to_IP(df, ColNames.ADAPTIVE_COMFORT) - convert_SI_to_IP(df, ColNames.ADAPTIVE_CMF_80_LOW) - convert_SI_to_IP(df, ColNames.ADAPTIVE_CMF_80_UP) - convert_SI_to_IP(df, ColNames.ADAPTIVE_CMF_90_LOW) - convert_SI_to_IP(df, ColNames.ADAPTIVE_CMF_90_UP) - mapping_dict = json.loads(mapping_json) - for key in json.loads(mapping_json): - if ColNames.CONVERSION_FUNCTION in mapping_dict[key]: - conversion_function_name = mapping_dict[key][ColNames.CONVERSION_FUNCTION] - if conversion_function_name is not None: + for key, meta in mapping_dict.items(): + if meta.get(ColNames.CONVERSION_FUNCTION): + if key in df.columns: convert_SI_to_IP(df, key) - return json.dumps(mapping_dict) + return mapping_json def expand_to_hours(value: any, hours: int = 8760) -> list[any]: From aaca5ab6424744f043a6edd1f907c4f37b75dc15 Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Tue, 9 Sep 2025 18:15:09 +1000 Subject: [PATCH 079/163] refactor: simplify unit conversion logic and add fallback handling --- pages/lib/extract_df.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 6f5f8fd1..2e4ba5f9 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -402,6 +402,9 @@ def create_df(lst, file_name): def convert_SI_to_IP(df: pd.DataFrame, name: str) -> None: """Convert SI to IP based on column name.""" if name not in df.columns: + print( + f"[convert_SI_to_IP] Column '{name}' not found in DataFrame. Skipping conversion." + ) return match name: case ( @@ -448,13 +451,15 @@ def convert_SI_to_IP(df: pd.DataFrame, name: str) -> None: case ColNames.EH: df[name] = df[name] * 0.000429923 + case _: + # No conversion needed for this column + pass + def convert_data(df, mapping_json): mapping_dict = json.loads(mapping_json) - for key, meta in mapping_dict.items(): - if meta.get(ColNames.CONVERSION_FUNCTION): - if key in df.columns: - convert_SI_to_IP(df, key) + for key in mapping_dict: + convert_SI_to_IP(df, key) return mapping_json From 7a7f3116a0e70fdab9be1f764e5d85d9aaad5df3 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Wed, 10 Sep 2025 14:35:26 +1000 Subject: [PATCH 080/163] refactor(layout): move tabs links to collapsible sidebar using dash-mantine-components - updated dash and dash-mantine-components - moved the navigation and document, global, and IP to the sidebar - modified the spec.cy.js - updated the tabs.css and layout.css --- Pipfile | 4 +- Pipfile.lock | 61 ++--- assets/layout.css | 11 + assets/tabs.css | 130 +++++++++++ main.py | 18 +- pages/explorer.py | 103 ++++----- pages/lib/global_element_ids.py | 19 ++ pages/lib/layout.py | 354 +++++++++++++++++++----------- pages/lib/utils.py | 9 +- pages/natural_ventilation.py | 37 ++-- pages/not_found_404.py | 2 +- pages/outdoor.py | 33 +-- pages/psy-chart.py | 33 +-- pages/select.py | 27 ++- pages/summary.py | 17 +- pages/sun.py | 29 +-- pages/t_rh.py | 23 +- pages/wind.py | 85 +++---- requirements.txt | 4 +- tests/node/cypress/e2e/spec.cy.js | 34 ++- 20 files changed, 654 insertions(+), 379 deletions(-) diff --git a/Pipfile b/Pipfile index 91288833..0c01f3de 100644 --- a/Pipfile +++ b/Pipfile @@ -4,12 +4,12 @@ verify_ssl = true name = "pypi" [packages] -dash = "==2.15" +dash = "==3.2" pvlib = "==0.9.1" pythermalcomfort = "==2.9.1" dash-bootstrap-components = "==1.2.0" dash-extensions = "==1.0.7" -dash-mantine-components = "==0.12.1" +dash-mantine-components = "==2.2.1" requests = "==2.32.4" plotly = "==5.18.0" pandas = "==2.2.0" diff --git a/Pipfile.lock b/Pipfile.lock index 15c208f9..8e475b6d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7214a1158f64483648ecff36a57b61b67020408e0f16770b77d7165a9d6f47e0" + "sha256": "4f09fe1d5fd82b15503fd3b0c1606e5e6139df983488fdc78c72555daf01e167" }, "pipfile-spec": 6, "requires": { @@ -133,22 +133,14 @@ "markers": "python_version >= '3.10'", "version": "==8.2.1" }, - "colorama": { - "hashes": [ - "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", - "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==0.4.6" - }, "dash": { "hashes": [ - "sha256:d38891337fc855d5673f75e5346354daa063c4ff45a8a6a21f25e858fcae41c2", - "sha256:df1882bbf613e4ca4372281c8facbeb68e97d76720336b051bf84c75d2de8588" + "sha256:4c1819588d83bed2cbcf5807daa5c2380c8c85789a6935a733f018f04ad8a6a2", + "sha256:93300b9b99498f8b8ed267e61c455b4ee1282c7e4d4b518600eec87ce6ddea55" ], "index": "pypi", - "markers": "python_version >= '3.6'", - "version": "==2.15.0" + "markers": "python_version >= '3.8'", + "version": "==3.2.0" }, "dash-bootstrap-components": { "hashes": [ @@ -159,13 +151,6 @@ "markers": "python_version >= '3.6' and python_version < '4'", "version": "==1.2.0" }, - "dash-core-components": { - "hashes": [ - "sha256:52b8e8cce13b18d0802ee3acbc5e888cb1248a04968f962d63d070400af2e346", - "sha256:c6733874af975e552f95a1398a16c2ee7df14ce43fa60bb3718a3c6e0b63ffee" - ], - "version": "==2.0.0" - }, "dash-extensions": { "hashes": [ "sha256:46c4ec7c7d3b42db63032005f7d258435bc0092d0fec6f6ce000be68befeb102", @@ -175,13 +160,6 @@ "markers": "python_version >= '3.8' and python_version < '4'", "version": "==1.0.7" }, - "dash-html-components": { - "hashes": [ - "sha256:8703a601080f02619a6390998e0b3da4a5daabe97a1fd7a9cebc09d015f26e50", - "sha256:b42cc903713c9706af03b3f2548bda4be7307a7cf89b7d6eae3da872717d1b63" - ], - "version": "==2.0.0" - }, "dash-iconify": { "hashes": [ "sha256:564774be6b11b0ac3a8999b7137c3d17a1d351d69b673aa313c7228eacc9d143", @@ -192,18 +170,11 @@ }, "dash-mantine-components": { "hashes": [ - "sha256:2630bca31cb96d96fb2c4f986e639b9f92d6319aba8cba02f76da6c0d8f5ca48", - "sha256:c3dcbfd89813a1539654b8d016eb953dc5f67aafe1a77d45b5ec9faa6f25d3e7" + "sha256:a54acdb8b3e7a80251e9d68947a84b0a1d16285f5ca33363dbc29f287a13ebaa", + "sha256:cd3aefd3191be365db0435067196fe037e3803c996794c174f0f0409c24efac4" ], "index": "pypi", - "version": "==0.12.1" - }, - "dash-table": { - "hashes": [ - "sha256:18624d693d4c8ef2ddec99a6f167593437a7ea0bf153aa20f318c170c5bc7308", - "sha256:19036fa352bb1c11baf38068ec62d172f0515f73ca3276c79dee49b95ddc16c9" - ], - "version": "==5.0.0" + "version": "==2.2.1" }, "dataclass-wizard": { "hashes": [ @@ -635,7 +606,7 @@ "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", "version": "==1.17.0" }, "tenacity": { @@ -648,11 +619,11 @@ }, "typing-extensions": { "hashes": [ - "sha256:38b39f4aeeab64884ce9f74c94263ef78f3c22467c8724005483154c26648d36", - "sha256:d1e1e3b58374dc93031d6eda2420a48ea44a36c2b4766a4fdeb3710755731d76" + "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", + "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548" ], "markers": "python_version >= '3.9'", - "version": "==4.14.1" + "version": "==4.15.0" }, "tzdata": { "hashes": [ @@ -672,11 +643,11 @@ }, "werkzeug": { "hashes": [ - "sha256:1bc0c2310d2fbb07b1dd1105eba2f7af72f322e1e455f2f93c993bee8c8a5f17", - "sha256:a8dd59d4de28ca70471a34cba79bed5f7ef2e036a76b3ab0835474246eb41f8d" + "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", + "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746" ], - "markers": "python_version >= '3.8'", - "version": "==3.0.6" + "markers": "python_version >= '3.9'", + "version": "==3.1.3" }, "zipp": { "hashes": [ diff --git a/assets/layout.css b/assets/layout.css index e9089dbc..611c3a11 100644 --- a/assets/layout.css +++ b/assets/layout.css @@ -29,4 +29,15 @@ width: 100%; //or any percentage width you want } +/* begin displaying banner below the banner */ +.custom-sidebar .mantine-Drawer-content { + top: 80px !important; + height: calc(100vh - 80px) !important; + position: fixed !important; +} + +.custom-sidebar .mantine-Drawer-overlay { + top: 80px !important; + height: calc(100vh - 80px) !important; +} diff --git a/assets/tabs.css b/assets/tabs.css index 15590dff..7ba0edbc 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -285,4 +285,134 @@ p { border-radius: 0.25rem; border: 0.5px solid lightgrey; z-index: 1000; +} + +/* side bar */ +#sidebar { + background-color: #f8f9fa; + border-right: 1px solid #e9ecef; +} + +/* 强制SegmentedControl适应容器宽度 */ +#sidebar .mantine-SegmentedControl-root { + width: 100% !important; +} + +#sidebar .mantine-SegmentedControl-control { + flex: 1 !important; + min-width: 0 !important; +} + +/* burger button */ +#burger-button { + transition: all 0.2s ease; +} + +#burger-button:hover { + transform: scale(1.05); +} + +.nav-link { + border-radius: 6px; + transition: all 0.2s ease; +} + +.nav-link:hover { + background-color: #e3f2fd; +} + +/* Active navigation link styles */ +.nav-link[data-active="true"] { + background-color: #1976d2 !important; + color: white !important; + font-weight: 600; +} + +.nav-link[data-active="true"]:hover { + background-color: #1565c0 !important; + color: white !important; +} + +/* response design */ +@media (max-width: 768px) { + #sidebar { + width: 280px !important; + } +} + +/* burger button style */ +#burger-button { + box-shadow: 0 4px 8px rgba(0,0,0,0.1); +} + +/* Alert style */ +.survey-alert { + position: fixed; + top: 25px; + right: 10px; + width: 400px; +} + +/* Footer style */ +#footer-container { + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: nowrap; + width: 100%; + min-height: auto; + padding: 10px 0; +} + +/* Footer Logo style */ +.footer-logo-section { + flex: 0 0 33.333333%; + max-width: 33.333333%; + padding: 10px 15px 10px 25px; + display: flex; + align-items: center; + justify-content: flex-start; +} + +/* Footer style */ +.footer-content-section { + flex: 0 0 66.666667%; + max-width: 66.666667%; + padding: 10px 15px; + display: flex; + align-items: center; + justify-content: flex-start; +} + +/* Footer text style */ +.footer-text-content { + margin-top: 1rem; +} + +/* Footer Markdown style */ +.footer-markdown-text { + font-size: 16px; + line-height: 1.5; + font-weight: 500; +} + +/* Footer link style */ +.footer-links-group { + margin-top: 1rem; +} + +.footer-link { + text-decoration: underline; + font-size: 16px; + font-weight: 500; +} + +/* Banner style */ +#banner-title { + line-height: 1.1; +} + +/* Documentation style */ +#nav-doc-link { + margin-top: 5px; } \ No newline at end of file diff --git a/main.py b/main.py index e5c23662..6fa4b598 100644 --- a/main.py +++ b/main.py @@ -1,22 +1,26 @@ import dash_bootstrap_components as dbc from dash import html, dcc from dash_extensions.enrich import Output, Input, callback +import dash_mantine_components as dmc from app import app -from pages.lib.layout import banner, footer, build_tabs +from pages.lib.layout import banner, footer, build_tabs, burger_button, sidebar from config import AppConfig from pages.lib.global_element_ids import ElementIds server = app.server app.title = AppConfig.TITLE -app.layout = dbc.Container( - fluid=True, - style={"padding": "0"}, +app.layout = dmc.MantineProvider( + theme={ + "colorScheme": "light", + "primaryColor": "blue" + }, children=[ - dcc.Location(id="url", refresh=False), # connected to callback below + dcc.Location(id=ElementIds.MAIN_URL, refresh=False), + sidebar(), banner(), - html.Div(id="page-content", children=build_tabs()), + dmc.Box(id=ElementIds.PAGE_CONTENT, children=build_tabs()), footer(), ], ) @@ -32,7 +36,7 @@ def display_alert(n): if __name__ == "__main__": - app.run_server( + app.run( debug=AppConfig.DEBUG, host=AppConfig.HOST, port=AppConfig.PORT, diff --git a/pages/explorer.py b/pages/explorer.py index dde7df5c..8c27421d 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -1,5 +1,6 @@ import dash from dash import dcc, html +import dash_mantine_components as dmc import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback from dash.exceptions import PreventUpdate @@ -64,7 +65,7 @@ def section_one_inputs(): """Return the inputs from section one.""" - return html.Div( + return dmc.Box( className="container-row full-width row-center", children=[ html.H4(className="text-next-to-input", children=["Select a variable: "]), @@ -79,11 +80,11 @@ def section_one_inputs(): def section_one(): """Return the graphs for section one""" - return html.Div( + return dmc.Box( className="container-col full-width", children=[ section_one_inputs(), - html.Div( + dmc.Box( children=title_with_link( text="Yearly chart", id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL, @@ -92,9 +93,9 @@ def section_one(): ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.YEARLY_EXPLORE, className="full-width"), + children=dmc.Box(id=ElementIds.YEARLY_EXPLORE, className="full-width"), ), - html.Div( + dmc.Box( children=title_with_link( text="Daily chart", id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL, @@ -102,10 +103,10 @@ def section_one(): ), ), dcc.Loading( - html.Div(className="full-width", id=ElementIds.QUERY_DAILY), + dmc.Box(className="full-width", id=ElementIds.QUERY_DAILY), type="circle", ), - html.Div( + dmc.Box( children=title_with_link( text="Heatmap chart", id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL, @@ -113,20 +114,20 @@ def section_one(): ), ), dcc.Loading( - html.Div(className="full-width", id=ElementIds.QUERY_HEATMAP), + dmc.Box(className="full-width", id=ElementIds.QUERY_HEATMAP), type="circle", ), - html.Div( + dmc.Box( children=title_with_tooltip( text="Descriptive statistics", tooltip_text="count, mean, std, min, max, and percentiles", id_button=IdButtons.TABLE_EXPLORE, ), ), - html.Div( + dmc.Box( className="container-row justify-content-center", children=[ - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -136,13 +137,13 @@ def section_one(): className="mb-2", n_clicks=0, ), - html.Div( + dmc.Box( className=( "container-row full-width justify-center mt-2" ), children=[ html.H6("Month Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.SEC1_MONTH_SLIDER, min=1, @@ -168,11 +169,11 @@ def section_one(): ), ], ), - html.Div( + dmc.Box( className="container-row justify-center", children=[ html.H6("Hour Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.SEC1_HOUR_SLIDER, min=0, @@ -202,7 +203,7 @@ def section_one(): ), ], ), - html.Div( + dmc.Box( id=ElementIds.TABLE_DATA_EXPLORER, ), ], @@ -211,22 +212,22 @@ def section_one(): def section_two_inputs(): """Return all the input forms from section two.""" - return html.Div( + return dmc.Box( children=[ - html.Div( + dmc.Box( children=title_with_tooltip( text="Customizable heatmap", tooltip_text=None, id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL, ), ), - html.Div( + dmc.Box( className="container-row full-width three-inputs-container", children=[ - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -243,7 +244,7 @@ def section_two_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -253,13 +254,13 @@ def section_two_inputs(): className="mb-2", n_clicks=0, ), - html.Div( + dmc.Box( className=( "container-row full-width justify-center mt-2" ), children=[ html.H6("Month Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.SEC2_MONTH_SLIDER, min=1, @@ -285,11 +286,11 @@ def section_two_inputs(): ), ], ), - html.Div( + dmc.Box( className="container-row justify-center", children=[ html.H6("Hour Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.SEC2_HOUR_SLIDER, min=0, @@ -317,7 +318,7 @@ def section_two_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -327,7 +328,7 @@ def section_two_inputs(): className="mb-2", n_clicks=0, ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -342,7 +343,7 @@ def section_two_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -358,7 +359,7 @@ def section_two_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -384,14 +385,14 @@ def section_two_inputs(): def section_two(): """Return the two graphs in section two.""" - return html.Div( + return dmc.Box( id=ElementIds.TAB6_SEC2_CONTAINER, className="container-col justify-center full-width", children=[ section_two_inputs(), dcc.Loading( type="circle", - children=html.Div(className="full-width", id=ElementIds.CUSTOM_HEATMAP), + children=dmc.Box(className="full-width", id=ElementIds.CUSTOM_HEATMAP), ), dbc.Checklist( options=[ @@ -416,13 +417,13 @@ def section_two(): def section_three_inputs(): """""" - return html.Div( + return dmc.Box( className="container-row full-width three-inputs-container", children=[ - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6(style={"flex": "30%"}, children=["X Variable:"]), @@ -434,7 +435,7 @@ def section_three_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6(style={"flex": "30%"}, children=["Y Variable:"]), @@ -446,7 +447,7 @@ def section_three_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6(style={"flex": "30%"}, children=["Color By:"]), @@ -460,7 +461,7 @@ def section_three_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -470,11 +471,11 @@ def section_three_inputs(): className="mb-2", n_clicks=0, ), - html.Div( + dmc.Box( className="container-row full-width justify-center", children=[ html.H6("Month Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, min=1, @@ -500,11 +501,11 @@ def section_three_inputs(): ), ], ), - html.Div( + dmc.Box( className="container-row full-width justify-center", children=[ html.H6("Hour Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, min=0, @@ -532,7 +533,7 @@ def section_three_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -542,7 +543,7 @@ def section_three_inputs(): className="mb-2", n_clicks=0, ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -556,7 +557,7 @@ def section_three_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6(children=["Min Value:"], style={"flex": "30%"}), @@ -571,7 +572,7 @@ def section_three_inputs(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6(children=["Max Value:"], style={"flex": "30%"}), @@ -594,10 +595,10 @@ def section_three_inputs(): def section_three(): """Return the two graphs in section three.""" - return html.Div( + return dmc.Box( className="container-col full-width", children=[ - html.Div( + dmc.Box( children=title_with_tooltip( text="More charts", tooltip_text=None, @@ -606,11 +607,11 @@ def section_three(): ), section_three_inputs(), dcc.Loading( - html.Div(id=ElementIds.THREE_VAR), + dmc.Box(id=ElementIds.THREE_VAR), type="circle", ), dcc.Loading( - html.Div(id=ElementIds.TWO_VAR), + dmc.Box(id=ElementIds.TWO_VAR), type="circle", ), ], @@ -619,7 +620,7 @@ def section_three(): def layout(): """Return the contents of tab six.""" - return html.Div( + return dmc.Box( className="justify-center", children=[section_one(), section_two(), section_three()], ) diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py index 644dcd3e..8c597743 100644 --- a/pages/lib/global_element_ids.py +++ b/pages/lib/global_element_ids.py @@ -223,3 +223,22 @@ class ElementIds(str, Enum): TABS = "tabs" STORE_CONTAINER = "store-container" TABS_CONTENT = "tabs-content" + BURGER_BUTTON = "burger-button" + SIDE_BAR = "sidebar" + NAV_GROUP_MAIN = "nav-group-main" + NAV_GROUP_CONTROLS = "nav-group-controls" + NAV_DOC_LINK = "nav-doc-link" + LAYOUT_URL = "url" + SELECT_URL = "url" + MAIN_URL = "url" + PAGE_CONTENT = "page-content" + NAV = "nav-" + NAV_SUMMARY = "nav-summary" + NAV_T_RH = "nav-t-rh" + NAV_SUN = "nav-sun" + NAV_WIND = "nav-wind" + NAV_PSY_CHART = "nav-psy-chart" + NAV_EXPLORER = "nav-explorer" + NAV_OUTDOOR = "nav-outdoor" + NAV_NATURAL_VENTILATION = "nav-natural-ventilation" + NAV_CHANGELOG = "nav-changelog" \ No newline at end of file diff --git a/pages/lib/layout.py b/pages/lib/layout.py index f6cff013..c988bcb4 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -1,6 +1,6 @@ import dash_bootstrap_components as dbc import dash -from dash import dcc, html +from dash import dcc, html, Input, Output, State, callback import dash_mantine_components as dmc from dash_iconify import DashIconify from pages.lib.global_column_names import ColNames @@ -8,19 +8,30 @@ from pages.lib.global_element_ids import ElementIds +def burger_button(): + """create burger button""" + return dmc.ActionIcon( + DashIconify(icon="radix-icons:hamburger-menu", width=20), + id=ElementIds.BURGER_BUTTON, + size="lg", + variant="filled", + color="blue", + ) + + def alert(): - """Alert for survey.""" - return html.Div( - id=ElementIds.ALERT_CONTAINER, + """Survey toast + periodic timer.""" + return dmc.Stack( + gap=0, children=[ dbc.Toast( [ "If you have a moment, help us improve Clima and take a ", - html.A( + dmc.Anchor( "quick user survey", href="https://forms.gle/k289zP3R92jdu14M7", - className="alert-link", target="_blank", + className="alert-link", ), "! ☀️", ], @@ -30,12 +41,13 @@ def alert(): is_open=False, dismissable=True, className="survey-alert", - style={"position": "fixed", "top": 25, "right": 10, "width": 400}, ), - dcc.Interval( - id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, - interval=12 * 1000, - n_intervals=0, + dmc.Box( + children=dcc.Interval( + id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, + interval=12 * 1000, + n_intervals=0, + ) ), ], ) @@ -43,39 +55,37 @@ def alert(): def footer(): """Build the footer at the bottom of the page.""" - return dbc.Row( - align="center", - justify="between", + return dmc.Box( id=ElementIds.FOOTER_CONTAINER, children=[ - dbc.Col( + dmc.Box( children=[ - dbc.Row( - html.A( - children=[ - html.Img( - src="assets/img/cbe-logo.png", - ) - ], - href="https://cbe.berkeley.edu/", + dmc.Anchor( + href="https://cbe.berkeley.edu/", + children=dmc.Image( + src="assets/img/cbe-logo.png", + alt="CBE Logo", + h=65, + w="auto", + fit="contain" ) ), ], - width=12, - md=4, - style={"padding": "15px"}, + className="footer-logo-section" ), - dbc.Col( + dmc.Box( children=[ - dbc.Row( - [ + dmc.Stack( + gap="xs", + children=[ dcc.Markdown( """ Please cite us: - Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: - A free and open-source web application for climate analysis tailored to sustainable building design. + Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: + A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). - """ + """, + className="footer-markdown-text" ), dmc.Group( [ @@ -85,6 +95,7 @@ def footer(): underline=True, c="white", target="_blank", + className="footer-link" ), dmc.Anchor( "Contributors", @@ -92,6 +103,7 @@ def footer(): underline=True, c="white", target="_blank", + className="footer-link" ), dmc.Anchor( "Report issues on GitHub", @@ -99,6 +111,7 @@ def footer(): underline=True, c="white", target="_blank", + className="footer-link" ), dmc.Anchor( "Contact us", @@ -106,6 +119,7 @@ def footer(): underline=True, c="white", target="_blank", + className="footer-link" ), dmc.Anchor( "Documentation", @@ -113,6 +127,7 @@ def footer(): underline=True, c="white", target="_blank", + className="footer-link" ), dmc.Anchor( "License", @@ -120,110 +135,181 @@ def footer(): underline=True, c="white", target="_blank", + className="footer-link" ), ], - spacing="sm", - style={"marginTop": "1rem"}, + gap="sm", + className="footer-links-group" ), ], - style={"marginTop": "1rem"}, + className="footer-text-content" ), ], - width=12, - md=8, + className="footer-content-section" ), ], ) def banner(): - """Build the banner at the top of the page.""" - return html.Div( + """Top banner rewritten with dash-mantine-components only.""" + return dmc.Box( id=ElementIds.BANNER, children=[ dmc.Group( - position="apart", + justify="space-between", align="center", + wrap="nowrap", children=[ dmc.Group( align="center", + gap="md", children=[ - html.A( - href="/", - children=[ - dmc.Image( - src="assets/img/cbe-logo-small.png", - height=40, - width="auto", - ) - ], - ), + burger_button(), + dmc.Image(src="assets/img/cbe-logo-small.png", h=40, w="auto"), dmc.Stack( - spacing=0, + gap=2, children=[ dmc.Title( "CBE Clima Tool", - order=1, id=ElementIds.BANNER_TITLE, - style={"fontSize": "2rem"}, + order=2, ), dmc.Text( - "Current Location:", + "Current Location: N/A", id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE, size="sm", + opacity=0.85, ), ], ), ], ), - dmc.Group( - align="center", - children=[ - html.A( - dmc.Button( - "Documentation", - leftIcon=DashIconify(icon="bi:book-half", width=20), - variant="filled", - color="#5c7cfa", - ), - href=DocLinks.MAIN.value, - target="_blank", - style={"textDecoration": "none"}, - ), - dmc.SegmentedControl( - id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT, - value="local", - radius="md", - data=[ - {"label": "Global Value Ranges", "value": "global"}, - {"label": "Local Value Ranges", "value": "local"}, - ], - ), - dmc.SegmentedControl( - id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT, - value=UnitSystem.SI, - radius="md", - data=[ - { - "label": UnitSystem.SI.upper(), - "value": UnitSystem.SI, - }, - { - "label": UnitSystem.IP.upper(), - "value": UnitSystem.IP, - }, - ], - ), - ], - ), ], ) ], ) +def sidebar(): + """ create side bar """ + return dmc.Drawer( + id=ElementIds.SIDE_BAR, + title=dmc.Group([ + dmc.Image(src="assets/img/cbe-logo-small.png", h=30, w="auto"), + dmc.Text("CBE Clima Tool", fw=600) + ]), + padding="md", + size="300px", + zIndex=999, + opened=False, + className="custom-sidebar", + styles={ + "title": {"paddingRight": 30}, + }, + children=[ + dmc.Stack( + gap="sm", + children=build_sidebar_nav_items() + ) + ], + ) + + +# Pages Icon +PAGE_ICON_MAP = { + "Select Weather File": "tabler:upload", + "Climate Summary": "tabler:chart-bar", + "Temperature and Humidity": "tabler:temperature", + "Sun and Clouds": "tabler:sun", + "Wind": "tabler:wind", + "Psychrometric Chart": "tabler:chart-dots", + "Natural Ventilation": "tabler:windmill", + "Outdoor Comfort": "tabler:thermometer", + "Data Explorer": "tabler:database", + "Changelog": "tabler:history" +} + +def build_sidebar_nav_items(): + # === Secondary Menu === + sub_links = [] + for page in dash.page_registry.values(): + if page[ColNames.NAME] in ["404"]: + continue + icon = PAGE_ICON_MAP.get(page[ColNames.NAME], "tabler:circle") + sub_links.append( + dmc.NavLink( + label=page[ColNames.NAME], + leftSection=DashIconify(icon=icon, width=20), + href=page[ColNames.PATH], + id=f"nav-{page[ColNames.PATH].replace('/', '')}", + active=False, + style={"marginBottom": "4px"}, + ) + ) + + # Primary Menu + parent_group = dmc.NavLink( + label="Pages Menu", + leftSection=DashIconify(icon="tabler:list-details", width=20), + children=sub_links, + id=ElementIds.NAV_GROUP_MAIN, + variant="light", + childrenOffset=18, + ) + + controls_stack = dmc.Stack( + gap="sm", + px=0, + py="xs", + children=[ + dmc.SegmentedControl( + id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT, + value="local", + data=[ + {"label": "Global Value Ranges", "value": "global"}, + {"label": "Local Value Ranges", "value": "local"}, + ], + radius="md", + size="sm", + ), + dmc.SegmentedControl( + id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT, + value=UnitSystem.SI, + data=[ + {"label": UnitSystem.SI.upper(), "value": UnitSystem.SI}, + {"label": UnitSystem.IP.upper(), "value": UnitSystem.IP}, + ], + radius="md", + size="sm", + ), + ], + ) + + # Primary Menu + controls_group = dmc.NavLink( + label="Tools Menu", + leftSection=DashIconify(icon="tabler:settings", width=20), + children=[controls_stack], + id=ElementIds.NAV_GROUP_CONTROLS, + variant="light", + childrenOffset=18, + ) + + # Primary Menu - Documentation + doc_link = dmc.NavLink( + label="Documentation", + leftSection=DashIconify(icon="tabler:file-text", width=20), + href=DocLinks.MAIN.value, + target="_blank", + id=ElementIds.NAV_DOC_LINK, + variant="light", + ) + return [parent_group, controls_group, doc_link] + + def store(): - return html.Div( + return dmc.Box( id=ElementIds.STORE, children=[ dcc.Store(id=ElementIds.ID_LAYOUT_DF_STORE, storage_type="session"), @@ -236,44 +322,17 @@ def store(): def build_tabs(): - return html.Div( + return dmc.Box( id=ElementIds.TABS_CONTAINER, children=[ - html.Div( - id=ElementIds.TABS_PARENT, - className="custom-tabs", - children=[ - dbc.Nav( - [ - dbc.NavItem( - dbc.NavLink( - page[ColNames.NAME], - id=page[ColNames.PATH], - href=page[ColNames.PATH], - active="exact", - className="nav-link", - disabled=True, - ), - className="custom-tab", - ) - for page in dash.page_registry.values() - if page[ColNames.NAME] not in ["404", "changelog"] - ], - id=ElementIds.TABS, - class_name="tab-container", - pills=True, - justified=True, - ) - ], - ), - html.Div( + dmc.Box( id=ElementIds.STORE_CONTAINER, children=[ store(), - html.Div( + dmc.Box( id=ElementIds.TABS_CONTENT, children=[ - alert(), # alert can be removed after survey is done + alert(), dash.page_container, ], ), @@ -281,3 +340,46 @@ def build_tabs(): ), ], ) + +@callback( + Output(ElementIds.SIDE_BAR, "opened"), + Input(ElementIds.BURGER_BUTTON, "n_clicks"), + State(ElementIds.SIDE_BAR, "opened"), + prevent_initial_call=True, +) +def toggle_sidebar(n_clicks, opened): + if n_clicks: + return not opened + return opened + + +@callback( + Output(ElementIds.SIDE_BAR, "opened", allow_duplicate=True), + Input(ElementIds.LAYOUT_URL, "pathname"), + prevent_initial_call='initial_duplicate', +) +def close_sidebar_on_navigation(pathname): + return False + + +# Callback to set active state for navigation links based on current URL +@callback( + [Output(f"nav-{page[ColNames.PATH].replace('/', '')}", "active") + for page in dash.page_registry.values() + if page[ColNames.NAME] not in ["404"]], + Input(ElementIds.LAYOUT_URL, "pathname"), + prevent_initial_call=True, +) +def update_nav_active_state(pathname): + """Update active state of navigation links based on current URL pathname""" + active_states = [] + + for page in dash.page_registry.values(): + if page[ColNames.NAME] in ["404"]: + continue + + # Check if current pathname matches this page's path + is_active = pathname == page[ColNames.PATH] + active_states.append(is_active) + + return active_states diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 539ca233..281c4f59 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -6,6 +6,7 @@ import dash_bootstrap_components as dbc import pandas as pd from dash import html, dash_table, dcc +import dash_mantine_components as dmc from config import UnitSystem from pages.lib.global_scheme import fig_config, mapping_dictionary, month_lst @@ -151,12 +152,12 @@ def title_with_tooltip(text, tooltip_text, id_button): if tooltip_text: display_tooltip = "block" - return html.Div( + return dmc.Box( className="container-row", style={"padding": "1rem", "marginTop": "1rem"}, children=[ html.H5(text, style={"marginRight": "0.5rem"}), - html.Div( + dmc.Box( [ html.Sup( html.Img( @@ -187,12 +188,12 @@ def title_with_link( id_button=None, doc_link: str = "", ): - return html.Div( + return dmc.Box( className="container-row", style={"padding": "1rem", "marginTop": "1rem"}, children=[ html.H5(text, style={"marginRight": "0.5rem"}), - html.Div( + dmc.Box( [ html.Sup( html.A( diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index ee002e20..67f21714 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,5 +1,8 @@ +import math + import dash from dash import dcc, html +import dash_mantine_components as dmc import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback @@ -41,7 +44,7 @@ def layout(): - return html.Div( + return dmc.Box( className="container-col", id=ElementIds.MAIN_NV_SECTION, children=[ @@ -65,7 +68,7 @@ def update_layout(si_ip): dpt_set = 16 return [ - html.Div( + dmc.Box( children=title_with_link( text="Natural Ventilation Potential", id_button=IdButtons.NATURAL_VENTILATION_LABEL, @@ -74,13 +77,13 @@ def update_layout(si_ip): ), inputs_tab(tdb_set_min, tdb_set_max, dpt_set), dcc.Loading( - html.Div( + dmc.Box( id=ElementIds.NV_HEATMAP_CHART, style={"marginTop": "1rem"}, ), type="circle", ), - html.Div( + dmc.Box( className="container-row align-center justify-center", children=[ dbc.Checklist( @@ -96,7 +99,7 @@ def update_layout(si_ip): "marginRight": "-2rem", }, ), - html.Div( + dmc.Box( children=title_with_tooltip( text="Normalize data", tooltip_text=( @@ -109,7 +112,7 @@ def update_layout(si_ip): ], ), dcc.Loading( - html.Div( + dmc.Box( id=ElementIds.NV_BAR_CHART, style={"marginTop": "1rem"}, ), @@ -119,10 +122,10 @@ def update_layout(si_ip): def inputs_tab(t_min, t_max, d_set): - return html.Div( + return dmc.Box( className="container-row full-width three-inputs-container", children=[ - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -133,7 +136,7 @@ def inputs_tab(t_min, t_max, d_set): n_clicks=1, ), html.H6("Outdoor dry-bulb air temperature range"), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6(children=["Min Value:"], style={"flex": "30%"}), @@ -147,7 +150,7 @@ def inputs_tab(t_min, t_max, d_set): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6(children=["Max Value:"], style={"flex": "30%"}), @@ -163,7 +166,7 @@ def inputs_tab(t_min, t_max, d_set): ), ], ), - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -173,11 +176,11 @@ def inputs_tab(t_min, t_max, d_set): className="mb-2", n_clicks=0, ), - html.Div( + dmc.Box( className="container-row full-width justify-center mt-2", children=[ html.H6("Month Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.NV_MONTH_SLIDER, min=1, @@ -203,11 +206,11 @@ def inputs_tab(t_min, t_max, d_set): ), ], ), - html.Div( + dmc.Box( className="container-row align-center justify-center", children=[ html.H6("Hour Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.NV_HOUR_SLIDER, min=0, @@ -235,7 +238,7 @@ def inputs_tab(t_min, t_max, d_set): ), ], ), - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -261,7 +264,7 @@ def inputs_tab(t_min, t_max, d_set): value=[], id=ElementIds.ENABLE_CONDENSATION, ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( diff --git a/pages/not_found_404.py b/pages/not_found_404.py index ff3b389c..61e22382 100644 --- a/pages/not_found_404.py +++ b/pages/not_found_404.py @@ -27,7 +27,7 @@ dmc.Button( "Home page", fullWidth=True, - leftIcon=DashIconify(icon="material-symbols:home-outline-rounded"), + leftSection=DashIconify(icon="material-symbols:home-outline-rounded"), ), href=PageUrls.SELECT.value, ), diff --git a/pages/outdoor.py b/pages/outdoor.py index 0e4885ad..7e8335fc 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -1,5 +1,6 @@ import dash from dash import dcc, html +import dash_mantine_components as dmc import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback @@ -43,7 +44,7 @@ def inputs_outdoor_comfort(): md=6, sm=12, children=[ - html.Div( + dmc.Box( className="container-row center-block", children=[ html.H4( @@ -56,7 +57,7 @@ def inputs_outdoor_comfort(): options=outdoor_dropdown_names, value="utci_Sun_Wind", ), - html.Div( + dmc.Box( id=ElementIds.IMAGE_SELECTION, style={"flex": "10%"} ), ], @@ -77,11 +78,11 @@ def inputs_outdoor_comfort(): className="mb-2", n_clicks=0, ), - html.Div( + dmc.Box( className="container-row full-width justify-center mt-2", children=[ html.H6("Month Range", style={"flex": "5%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, min=1, @@ -107,11 +108,11 @@ def inputs_outdoor_comfort(): ), ], ), - html.Div( + dmc.Box( className="container-row align-center justify-center", children=[ html.H6("Hour Range", style={"flex": "5%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, min=0, @@ -144,10 +145,10 @@ def inputs_outdoor_comfort(): def outdoor_comfort_chart(): - return html.Div( + return dmc.Box( children=[ - html.Div(id=ElementIds.OUTDOOR_COMFORT_OUTPUT), - html.Div( + dmc.Box(id=ElementIds.OUTDOOR_COMFORT_OUTPUT), + dmc.Box( children=title_with_link( text="UTCI heatmap chart", id_button=IdButtons.UTCI_CHARTS_LABEL, @@ -155,10 +156,10 @@ def outdoor_comfort_chart(): ) ), dcc.Loading( - html.Div(id=ElementIds.UTCI_HEATMAP), + dmc.Box(id=ElementIds.UTCI_HEATMAP), type="circle", ), - html.Div( + dmc.Box( children=title_with_link( text="UTCI thermal stress chart", id_button=IdButtons.UTCI_CHARTS_LABEL, @@ -166,10 +167,10 @@ def outdoor_comfort_chart(): ) ), dcc.Loading( - html.Div(id=ElementIds.UTCI_CATEGORY_HEATMAP), + dmc.Box(id=ElementIds.UTCI_CATEGORY_HEATMAP), type="circle", ), - html.Div( + dmc.Box( className="container-row align-center justify-center", children=[ dbc.Checklist( @@ -185,7 +186,7 @@ def outdoor_comfort_chart(): "marginRight": "-2rem", }, ), - html.Div( + dmc.Box( children=title_with_tooltip( text="Normalize data", tooltip_text=( @@ -198,7 +199,7 @@ def outdoor_comfort_chart(): ], ), dcc.Loading( - html.Div(id=ElementIds.UTCI_SUMMARY_CHART), + dmc.Box(id=ElementIds.UTCI_SUMMARY_CHART), type="circle", ), ], @@ -209,7 +210,7 @@ def layout(): return ( dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( className="container-col", children=[inputs_outdoor_comfort(), outdoor_comfort_chart()], ), diff --git a/pages/psy-chart.py b/pages/psy-chart.py index bf967751..33af12c6 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -1,5 +1,6 @@ import dash from dash import dcc, html +import dash_mantine_components as dmc import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback @@ -61,13 +62,13 @@ def inputs(): """""" - return html.Div( + return dmc.Box( className="container-row full-width three-inputs-container", children=[ - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -86,7 +87,7 @@ def inputs(): ), ], ), - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -96,11 +97,11 @@ def inputs(): className="mb-2", n_clicks=0, ), - html.Div( + dmc.Box( className="container-row full-width justify-center mt-2", children=[ html.H6("Month Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.PSY_MONTH_SLIDER, min=1, @@ -126,11 +127,11 @@ def inputs(): ), ], ), - html.Div( + dmc.Box( className="container-row align-center justify-center", children=[ html.H6("Hour Range", style={"flex": "20%"}), - html.Div( + dmc.Box( dcc.RangeSlider( id=ElementIds.PSY_HOUR_SLIDER, min=0, @@ -158,7 +159,7 @@ def inputs(): ), ], ), - html.Div( + dmc.Box( className=container_col_center_one_of_three, children=[ dbc.Button( @@ -168,7 +169,7 @@ def inputs(): className="mb-2", n_clicks=0, ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -182,7 +183,7 @@ def inputs(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6(children=["Min Value:"], style={"flex": "30%"}), @@ -196,7 +197,7 @@ def inputs(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6(children=["Max Value:"], style={"flex": "30%"}), @@ -218,7 +219,7 @@ def inputs(): def layout(): return ( - html.Div( + dmc.Box( children=title_with_link( text="Psychrometric Chart", id_button=IdButtons.PSYCHROMETRIC_CHART_CHART, @@ -227,9 +228,9 @@ def layout(): ), dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( className="container-col", - children=[inputs(), html.Div(id=ElementIds.PSYCH_CHART)], + children=[inputs(), dmc.Box(id=ElementIds.PSYCH_CHART)], ), ), ) @@ -357,7 +358,7 @@ def update_psych_chart( if si_ip == UnitSystem.IP: for j in range(len(dbt_list)): - convert_SI_to_IP(dbt_list_convert, j) + dbt_list_convert[j] = dbt_list_convert[j] * 1.8 + 32 fig.add_trace( go.Scatter( diff --git a/pages/select.py b/pages/select.py index 86c22d87..330c8728 100644 --- a/pages/select.py +++ b/pages/select.py @@ -39,7 +39,7 @@ def layout(): """Contents in the first tab 'Select Weather File'""" - return html.Div( + return dmc.Box( className="container-col tab-container", children=[ dcc.Loading( @@ -69,7 +69,7 @@ def layout(): visible=False, id=ElementIds.SKELETON_GRAPH_CONTAINER, height=500, - children=html.Div(id=ElementIds.TAB_ONE_MAP), + children=dmc.Box(id=ElementIds.TAB_ONE_MAP), ), dbc.Modal( [ @@ -236,15 +236,16 @@ def switch_si_ip(_, si_ip_input, url_store, lines): @callback( [ - Output("/", "disabled"), - Output("/summary", "disabled"), - Output("/t-rh", "disabled"), - Output("/sun", "disabled"), - Output("/wind", "disabled"), - Output("/psy-chart", "disabled"), - Output("/explorer", "disabled"), - Output("/outdoor", "disabled"), - Output("/natural-ventilation", "disabled"), + Output(ElementIds.NAV, "disabled"), + Output(ElementIds.NAV_SUMMARY, "disabled"), + Output(ElementIds.NAV_T_RH, "disabled"), + Output(ElementIds.NAV_SUN, "disabled"), + Output(ElementIds.NAV_WIND, "disabled"), + Output(ElementIds.NAV_PSY_CHART, "disabled"), + Output(ElementIds.NAV_EXPLORER, "disabled"), + Output(ElementIds.NAV_OUTDOOR, "disabled"), + Output(ElementIds.NAV_NATURAL_VENTILATION, "disabled"), + Output(ElementIds.NAV_CHANGELOG, "disabled"), Output(ElementIds.ID_SELECT_BANNER_SUBTITLE, "children"), ], [ @@ -266,6 +267,7 @@ def enable_tabs_when_data_is_loaded(meta, data): True, True, True, + True, # changelog always disabled default, ) else: @@ -279,6 +281,7 @@ def enable_tabs_when_data_is_loaded(meta, data): False, False, False, + True, # changelog always disabled "Current Location: " + meta[ColNames.CITY] + ", " + meta[ColNames.COUNTRY], ) @@ -324,7 +327,7 @@ def change_text_modal(click_map): @callback( Output(ElementIds.SKELETON_GRAPH_CONTAINER, "children"), - Input("url", "pathname"), + Input(ElementIds.SELECT_URL, "pathname"), ) def plot_location_epw_files(pathname): # print(pathname) diff --git a/pages/summary.py b/pages/summary.py index de5412d9..832ab7e2 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -2,6 +2,7 @@ import dash_bootstrap_components as dbc from dash.exceptions import PreventUpdate from dash_extensions.enrich import dcc, html, Output, Input, State, callback +import dash_mantine_components as dmc import plotly.graph_objects as go import requests @@ -35,7 +36,7 @@ def layout(): """Contents in the second tab 'Climate Summary'.""" - return html.Div( + return dmc.Box( className="container-col", id=ElementIds.TAB_TWO_CONTAINER, children=[ @@ -56,13 +57,13 @@ def update_layout(si_ip): heating_setpoint = 50 cooling_setpoint = 64 - return html.Div( + return dmc.Box( className="container-col", id=ElementIds.TAB2_SCE1_CONTAINER, children=[ dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( className="container-col", id=ElementIds.LOCATION_INFO, style={"padding": "12px"}, @@ -70,9 +71,9 @@ def update_layout(si_ip): ), dcc.Loading( type="circle", - children=html.Div(className="tab-two-section", id=ElementIds.WORLD_MAP), + children=dmc.Box(className="tab-two-section", id=ElementIds.WORLD_MAP), ), - html.Div( + dmc.Box( children=title_with_tooltip( text="Download", id_button=IdButtons.DOWNLOAD_BUTTON_LABEL, @@ -109,7 +110,7 @@ def update_layout(si_ip): ], ), ), - html.Div( + dmc.Box( children=title_with_link( text="Heating and Cooling Degree Days", id_button=IdButtons.HDD_CDD_CHART, @@ -168,9 +169,9 @@ def update_layout(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER), + children=dmc.Box(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER), ), - html.Div( + dmc.Box( children=title_with_link( text="Climate Profiles", id_button=IdButtons.CLIMATE_PROFILES_CHART, diff --git a/pages/sun.py b/pages/sun.py index 919655f4..18794ff0 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -2,6 +2,7 @@ from pages.lib.global_element_ids import ElementIds import dash +import dash_mantine_components as dmc import dash_bootstrap_components as dbc import numpy as np from dash import html, dcc @@ -55,10 +56,10 @@ def sun_path(): """Return the layout for the custom sun path and its dropdowns.""" - return html.Div( + return dmc.Box( className="container-col justify-center", children=[ - html.Div( + dmc.Box( children=title_with_link( text="Sun path chart", id_button=IdButtons.SUN_PATH_CHART_LABEL, @@ -104,7 +105,7 @@ def sun_path(): ), dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( id=ElementIds.CUSTOM_SUNPATH, ), ), @@ -114,17 +115,17 @@ def sun_path(): def explore_daily_heatmap(): """Contents of the bottom part of the tab""" - return html.Div( + return dmc.Box( className="container-col full-width", children=[ - html.Div( + dmc.Box( children=title_with_link( text="Daily charts", id_button=IdButtons.DAILY_CHART_LABEL, doc_link=DocLinks.CUSTOM_HEATMAP, ), ), - html.Div( + dmc.Box( className="container-row justify-center align-center mb-2", children=[ html.H6( @@ -140,17 +141,17 @@ def explore_daily_heatmap(): ), ], ), - dcc.Loading(type="circle", children=html.Div(id=ElementIds.TAB4_DAILY)), + dcc.Loading(type="circle", children=dmc.Box(id=ElementIds.TAB4_DAILY)), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.TAB4_HEATMAP), + children=dmc.Box(id=ElementIds.TAB4_HEATMAP), ), ], ) def static_section(): - return html.Div( + return dmc.Box( id=ElementIds.STATIC_SECTION, className="container-col full-width", children=[ @@ -161,7 +162,7 @@ def static_section(): def layout(): """Contents of tab four.""" - return html.Div( + return dmc.Box( className="container-col", id=ElementIds.TAB_FOUR_CONTAINER, children=[sun_path(), static_section(), explore_daily_heatmap()], @@ -177,7 +178,7 @@ def update_static_section(si_ip): if si_ip == UnitSystem.IP: hor_unit = "Btu/ft²" return [ - html.Div( + dmc.Box( children=title_with_link( text="Global and Diffuse Horizontal Solar Radiation (" + hor_unit + ")", id_button=IdButtons.MONTHLY_CHART_LABEL, @@ -186,9 +187,9 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.MONTHLY_SOLAR), + children=dmc.Box(id=ElementIds.MONTHLY_SOLAR), ), - html.Div( + dmc.Box( children=title_with_link( text="Cloud coverage", id_button=IdButtons.CLOUD_CHART_LABEL, @@ -197,7 +198,7 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.CLOUD_COVER), + children=dmc.Box(id=ElementIds.CLOUD_COVER), ), ] diff --git a/pages/t_rh.py b/pages/t_rh.py index 6729a31d..4d249f81 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,5 +1,6 @@ import dash from dash_extensions.enrich import Output, Input, State, dcc, html, callback +import dash_mantine_components as dmc from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import dropdown_names from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile @@ -30,10 +31,10 @@ def layout(): - return html.Div( + return dmc.Box( className="container-col full-width", children=[ - html.Div( + dmc.Box( className="container-row full-width align-center justify-center", children=[ html.H4( @@ -47,10 +48,10 @@ def layout(): ), ], ), - html.Div( + dmc.Box( className="container-col", children=[ - html.Div( + dmc.Box( children=title_with_link( text="Yearly Chart", id_button=IdButtons.YEARLY_CHART_LABEL, @@ -59,9 +60,9 @@ def layout(): ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.YEARLY_CHART), + children=dmc.Box(id=ElementIds.YEARLY_CHART), ), - html.Div( + dmc.Box( children=title_with_link( text="Daily chart", id_button=IdButtons.DAILY_CHART_LABEL, @@ -70,9 +71,9 @@ def layout(): ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.DAILY), + children=dmc.Box(id=ElementIds.DAILY), ), - html.Div( + dmc.Box( children=title_with_link( text="Heatmap chart", id_button=IdButtons.HEATMAP_CHART_LABEL, @@ -81,16 +82,16 @@ def layout(): ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.HEATMAP), + children=dmc.Box(id=ElementIds.HEATMAP), ), - html.Div( + dmc.Box( children=title_with_tooltip( text="Descriptive statistics", tooltip_text="count, mean, std, min, max, and percentiles", id_button=IdButtons.TABLE_TMP_RH, ), ), - html.Div( + dmc.Box( id=ElementIds.TABLE_TMP_HUM, ), ], diff --git a/pages/wind.py b/pages/wind.py index 8b744447..da2813bc 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -1,5 +1,6 @@ import dash from dash import dcc, html +import dash_mantine_components as dmc from dash_extensions.enrich import Output, Input, State, callback from pages.lib.global_element_ids import ElementIds @@ -29,11 +30,11 @@ def sliders(): """Returns 2 sliders for the hour""" - return html.Div( + return dmc.Box( className="container-col justify-center", id=ElementIds.SLIDER_CONTAINER, children=[ - html.Div( + dmc.Box( className="container-row each-slider", children=[ html.P("Month Range"), @@ -49,7 +50,7 @@ def sliders(): ), ], ), - html.Div( + dmc.Box( className="container-row each-slider", children=[ html.P("Hour Range"), @@ -71,25 +72,25 @@ def sliders(): def seasonal_wind_rose(): """Return the section with the 4 seasonal wind rose graphs.""" - return html.Div( + return dmc.Box( className="container-col", children=[ - html.Div( + dmc.Box( children=title_with_link( text="Seasonal Wind Rose", id_button=IdButtons.SEASONAL_WIND_ROSE_DOC, doc_link=DocLinks.WIND_ROSE, ), ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ - html.Div( + dmc.Box( className="container-col", children=[ dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( id=ElementIds.WINTER_WIND_ROSE, className="daily-wind-graph", ), @@ -100,12 +101,12 @@ def seasonal_wind_rose(): ), ], ), - html.Div( + dmc.Box( className="container-col", children=[ dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( id=ElementIds.SPRING_WIND_ROSE, className="daily-wind-graph", ), @@ -118,15 +119,15 @@ def seasonal_wind_rose(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ - html.Div( + dmc.Box( className="container-col", children=[ dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( id=ElementIds.SUMMER_WIND_ROSE, className="daily-wind-graph", ), @@ -137,12 +138,12 @@ def seasonal_wind_rose(): ), ], ), - html.Div( + dmc.Box( className="container-col", children=[ dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( id=ElementIds.FALL_WIND_ROSE, className="daily-wind-graph", ), @@ -161,28 +162,28 @@ def seasonal_wind_rose(): def daily_wind_rose(): """Return the section for the 3 daily wind rose graphs.""" - return html.Div( + return dmc.Box( className="container-col full-width", id=ElementIds.TAB5_DAILY_CONTAINER, children=[ - html.Div( + dmc.Box( children=title_with_link( text="Daily Wind Rose", id_button=IdButtons.DAILY_ROSE_CHART, doc_link=DocLinks.WIND_ROSE, ), ), - html.Div( + dmc.Box( id=ElementIds.DAILY_WIND_ROSE_OUTER_CONTAINER, className="container-row full-width", children=[ - html.Div( + dmc.Box( className="container-col", children=[ - html.Div( + dmc.Box( dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( className="daily-wind-graph", id=ElementIds.MORNING_WIND_ROSE, ), @@ -194,13 +195,13 @@ def daily_wind_rose(): ), ], ), - html.Div( + dmc.Box( className="container-col", children=[ - html.Div( + dmc.Box( dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( className="daily-wind-graph", id=ElementIds.NOON_WIND_ROSE, ), @@ -212,13 +213,13 @@ def daily_wind_rose(): ), ], ), - html.Div( + dmc.Box( className="container-col", children=[ - html.Div( + dmc.Box( dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( className="daily-wind-graph", id=ElementIds.NIGHT_WIND_ROSE, ), @@ -237,24 +238,24 @@ def daily_wind_rose(): def custom_wind_rose(): - return html.Div( + return dmc.Box( className="container-col justify-center full-width", children=[ - html.Div( + dmc.Box( children=title_with_tooltip( text="Customizable Wind Rose", tooltip_text=None, id_button=IdButtons.CUSTOM_ROSE_CHART, ), ), - html.Div( + dmc.Box( className="container-row full-width justify-center", id=ElementIds.TAB5_CUSTOM_DROPDOWN_CONTAINER, children=[ - html.Div( + dmc.Box( className="container-col justify-center p-2 mr-2", children=[ - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -271,7 +272,7 @@ def custom_wind_rose(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -290,10 +291,10 @@ def custom_wind_rose(): ), ], ), - html.Div( + dmc.Box( className="container-col justify-center p-2 ml-2", children=[ - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -310,7 +311,7 @@ def custom_wind_rose(): ), ], ), - html.Div( + dmc.Box( className=container_row_center_full, children=[ html.H6( @@ -333,7 +334,7 @@ def custom_wind_rose(): ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.CUSTOM_WIND_ROSE), + children=dmc.Box(id=ElementIds.CUSTOM_WIND_ROSE), ), ], ) @@ -341,10 +342,10 @@ def custom_wind_rose(): def layout(): """Contents in the fifth tab 'Wind'.""" - return html.Div( + return dmc.Box( className="container-col justify-center", children=[ - html.Div( + dmc.Box( children=title_with_link( text="Annual Wind Rose", id_button=IdButtons.WIND_ROSE_LABEL, @@ -353,17 +354,17 @@ def layout(): ), dcc.Loading( type="circle", - children=html.Div( + children=dmc.Box( id=ElementIds.WIND_ROSE, ), ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.WIND_SPEED), + children=dmc.Box(id=ElementIds.WIND_SPEED), ), dcc.Loading( type="circle", - children=html.Div(id=ElementIds.WIND_DIRECTION), + children=dmc.Box(id=ElementIds.WIND_DIRECTION), ), seasonal_wind_rose(), daily_wind_rose(), diff --git a/requirements.txt b/requirements.txt index d2d7baaf..14cdb68c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,13 +7,13 @@ certifi==2025.7.14 charset-normalizer==3.4.2 cleanpy==0.5.1 click==8.2.1 -dash==2.15.0 +dash==3.2.0 dash-bootstrap-components==1.2.0 dash-core-components==2.0.0 dash-extensions==1.0.7 dash-html-components==2.0.0 dash-iconify==0.1.2 -dash-mantine-components==0.12.1 +dash-mantine-components==2.2.1 dash-table==5.0.0 dataclass-wizard==0.22.3 EditorConfig==0.17.1 diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index 8cb41333..b946fc72 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -1,7 +1,26 @@ +/* + ⚠️ IMPORTANT: This code is only needed when running tests with Cypress's default browser (Electron) + If you run tests with other browsers (e.g., Chrome, Firefox), comment out this entire block + The URL.canParse() API is not available in Cypress's bundled Node.js 18.17.1 environment + but is available in newer Node.js versions used by other browsers +*/ +/*Cypress.on('uncaught:exception', (err, runnable) => { + // Workaround for Cypress environment lacking support for `URL.canParse()` API + // This error does not happen in real browsers; it's safe to ignore during tests + if (err.message.includes('URL.canParse is not a function')) { + return false; + } +});*/ + function click_tab(name) { - cy.get('.nav-item') - .contains(name) - .click(); + // Open the sidebar (burger button is fixed on screen) + cy.get('#burger-button', { timeout: 10000 }).click({ force: true }); + // Expand the main nav group if collapsed + cy.get('#nav-group-main').click({ force: true }); + // Locate tab item by ID prefix, then find label by text + cy.get('[id^="nav-"]', { timeout: 10000 }).contains(name).click({ force: true }); + // Wait for tab content container to appear + cy.get('#tabs-content', { timeout: 20000 }).should('exist'); } function load_epw() { @@ -101,9 +120,14 @@ describe('Clima', () => { load_epw() cy.contains('The EPW was successfully loaded!'); click_tab('Temperature and Humidity') - cy.contains('Global Value Ranges').click(); + // Expand the "Data Display Options" nav section to access controls + cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true }); + cy.contains('Global Value Ranges', { timeout: 10000 }).click({ force: true }); cy.contains('-40'); // Global minimum: not something you see in Italy! - cy.contains('IP').click(); + // Reopen sidebar + cy.get('#burger-button', { timeout: 10000 }).click(); + cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true }); + cy.contains('IP').click({ force: true }); cy.contains('100'); // Not a Celsius temperature! cy.contains('Dry bulb temperature (°F)'); }); From ac8e71a20e0edcdf1f43c31036ce947ef12e351a Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Wed, 10 Sep 2025 14:41:43 +1000 Subject: [PATCH 081/163] style: formatted the code via using ruff and black. --- main.py | 10 ++--- pages/lib/global_element_ids.py | 2 +- pages/lib/layout.py | 73 +++++++++++++++++---------------- pages/lib/utils.py | 14 ++++--- pages/natural_ventilation.py | 2 - pages/psy-chart.py | 1 - 6 files changed, 50 insertions(+), 52 deletions(-) diff --git a/main.py b/main.py index 6fa4b598..e74a01af 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,9 @@ -import dash_bootstrap_components as dbc -from dash import html, dcc +from dash import dcc from dash_extensions.enrich import Output, Input, callback import dash_mantine_components as dmc from app import app -from pages.lib.layout import banner, footer, build_tabs, burger_button, sidebar +from pages.lib.layout import banner, footer, build_tabs, sidebar from config import AppConfig from pages.lib.global_element_ids import ElementIds @@ -12,10 +11,7 @@ app.title = AppConfig.TITLE app.layout = dmc.MantineProvider( - theme={ - "colorScheme": "light", - "primaryColor": "blue" - }, + theme={"colorScheme": "light", "primaryColor": "blue"}, children=[ dcc.Location(id=ElementIds.MAIN_URL, refresh=False), sidebar(), diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py index 8c597743..b9f227de 100644 --- a/pages/lib/global_element_ids.py +++ b/pages/lib/global_element_ids.py @@ -241,4 +241,4 @@ class ElementIds(str, Enum): NAV_EXPLORER = "nav-explorer" NAV_OUTDOOR = "nav-outdoor" NAV_NATURAL_VENTILATION = "nav-natural-ventilation" - NAV_CHANGELOG = "nav-changelog" \ No newline at end of file + NAV_CHANGELOG = "nav-changelog" diff --git a/pages/lib/layout.py b/pages/lib/layout.py index c988bcb4..c1fec70f 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -1,6 +1,6 @@ import dash_bootstrap_components as dbc import dash -from dash import dcc, html, Input, Output, State, callback +from dash import dcc, Input, Output, State, callback import dash_mantine_components as dmc from dash_iconify import DashIconify from pages.lib.global_column_names import ColNames @@ -67,11 +67,11 @@ def footer(): alt="CBE Logo", h=65, w="auto", - fit="contain" - ) + fit="contain", + ), ), ], - className="footer-logo-section" + className="footer-logo-section", ), dmc.Box( children=[ @@ -85,7 +85,7 @@ def footer(): A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). """, - className="footer-markdown-text" + className="footer-markdown-text", ), dmc.Group( [ @@ -95,7 +95,7 @@ def footer(): underline=True, c="white", target="_blank", - className="footer-link" + className="footer-link", ), dmc.Anchor( "Contributors", @@ -103,7 +103,7 @@ def footer(): underline=True, c="white", target="_blank", - className="footer-link" + className="footer-link", ), dmc.Anchor( "Report issues on GitHub", @@ -111,7 +111,7 @@ def footer(): underline=True, c="white", target="_blank", - className="footer-link" + className="footer-link", ), dmc.Anchor( "Contact us", @@ -119,7 +119,7 @@ def footer(): underline=True, c="white", target="_blank", - className="footer-link" + className="footer-link", ), dmc.Anchor( "Documentation", @@ -127,7 +127,7 @@ def footer(): underline=True, c="white", target="_blank", - className="footer-link" + className="footer-link", ), dmc.Anchor( "License", @@ -135,17 +135,17 @@ def footer(): underline=True, c="white", target="_blank", - className="footer-link" + className="footer-link", ), ], gap="sm", - className="footer-links-group" + className="footer-links-group", ), ], - className="footer-text-content" + className="footer-text-content", ), ], - className="footer-content-section" + className="footer-content-section", ), ], ) @@ -166,7 +166,9 @@ def banner(): gap="md", children=[ burger_button(), - dmc.Image(src="assets/img/cbe-logo-small.png", h=40, w="auto"), + dmc.Image( + src="assets/img/cbe-logo-small.png", h=40, w="auto" + ), dmc.Stack( gap=2, children=[ @@ -192,13 +194,15 @@ def banner(): def sidebar(): - """ create side bar """ + """create side bar""" return dmc.Drawer( id=ElementIds.SIDE_BAR, - title=dmc.Group([ - dmc.Image(src="assets/img/cbe-logo-small.png", h=30, w="auto"), - dmc.Text("CBE Clima Tool", fw=600) - ]), + title=dmc.Group( + [ + dmc.Image(src="assets/img/cbe-logo-small.png", h=30, w="auto"), + dmc.Text("CBE Clima Tool", fw=600), + ] + ), padding="md", size="300px", zIndex=999, @@ -207,12 +211,7 @@ def sidebar(): styles={ "title": {"paddingRight": 30}, }, - children=[ - dmc.Stack( - gap="sm", - children=build_sidebar_nav_items() - ) - ], + children=[dmc.Stack(gap="sm", children=build_sidebar_nav_items())], ) @@ -227,9 +226,10 @@ def sidebar(): "Natural Ventilation": "tabler:windmill", "Outdoor Comfort": "tabler:thermometer", "Data Explorer": "tabler:database", - "Changelog": "tabler:history" + "Changelog": "tabler:history", } + def build_sidebar_nav_items(): # === Secondary Menu === sub_links = [] @@ -268,7 +268,7 @@ def build_sidebar_nav_items(): value="local", data=[ {"label": "Global Value Ranges", "value": "global"}, - {"label": "Local Value Ranges", "value": "local"}, + {"label": "Local Value Ranges", "value": "local"}, ], radius="md", size="sm", @@ -341,6 +341,7 @@ def build_tabs(): ], ) + @callback( Output(ElementIds.SIDE_BAR, "opened"), Input(ElementIds.BURGER_BUTTON, "n_clicks"), @@ -356,7 +357,7 @@ def toggle_sidebar(n_clicks, opened): @callback( Output(ElementIds.SIDE_BAR, "opened", allow_duplicate=True), Input(ElementIds.LAYOUT_URL, "pathname"), - prevent_initial_call='initial_duplicate', + prevent_initial_call="initial_duplicate", ) def close_sidebar_on_navigation(pathname): return False @@ -364,22 +365,24 @@ def close_sidebar_on_navigation(pathname): # Callback to set active state for navigation links based on current URL @callback( - [Output(f"nav-{page[ColNames.PATH].replace('/', '')}", "active") - for page in dash.page_registry.values() - if page[ColNames.NAME] not in ["404"]], + [ + Output(f"nav-{page[ColNames.PATH].replace('/', '')}", "active") + for page in dash.page_registry.values() + if page[ColNames.NAME] not in ["404"] + ], Input(ElementIds.LAYOUT_URL, "pathname"), prevent_initial_call=True, ) def update_nav_active_state(pathname): """Update active state of navigation links based on current URL pathname""" active_states = [] - + for page in dash.page_registry.values(): if page[ColNames.NAME] in ["404"]: continue - + # Check if current pathname matches this page's path is_active = pathname == page[ColNames.PATH] active_states.append(is_active) - + return active_states diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 281c4f59..6ef56cd2 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -41,9 +41,9 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): ) figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = file_name else: - figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = ( - f"{tab_name}{custom_str}" - ) + figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ + ColNames.FILE_NAME + ] = f"{tab_name}{custom_str}" return figure_config @@ -248,9 +248,11 @@ def summary_table_tmp_rh_tab(df, value, si_ip): ) return dash_table.DataTable( columns=[ - {"name": i, "id": i} - if i == ColNames.MONTH - else {"name": f"{i} ({unit})", "id": i} + ( + {"name": i, "id": i} + if i == ColNames.MONTH + else {"name": f"{i} ({unit})", "id": i} + ) for i in df_summary.columns ], style_table={"overflowX": "auto"}, diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 67f21714..bf1bc88f 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,5 +1,3 @@ -import math - import dash from dash import dcc, html import dash_mantine_components as dmc diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 33af12c6..80b512be 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -12,7 +12,6 @@ from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.utils import get_max_min_value -from pages.lib.extract_df import convert_SI_to_IP from pages.lib.global_element_ids import ElementIds from pages.lib.global_column_names import ColNames from pages.lib.global_id_buttons import IdButtons From 7e4b4978b7090284176371981c8819777b3673c4 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Wed, 10 Sep 2025 14:46:13 +1000 Subject: [PATCH 082/163] fix: fixed the comment --- assets/tabs.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/tabs.css b/assets/tabs.css index 7ba0edbc..4e980f2e 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -293,7 +293,7 @@ p { border-right: 1px solid #e9ecef; } -/* 强制SegmentedControl适应容器宽度 */ +/* SegmentedControl adapt to container width */ #sidebar .mantine-SegmentedControl-root { width: 100% !important; } From 9ab10dfcce16d332bc51df4e4edfce22156f660a Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Wed, 10 Sep 2025 14:56:05 +1000 Subject: [PATCH 083/163] fix: re-formatted the file via using ruff --- pages/lib/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 6ef56cd2..4014db9a 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -41,9 +41,9 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): ) figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = file_name else: - figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ - ColNames.FILE_NAME - ] = f"{tab_name}{custom_str}" + figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = ( + f"{tab_name}{custom_str}" + ) return figure_config From dd356633f3a2d7b848e76eb22106008c6f0819e3 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:11:05 +1000 Subject: [PATCH 084/163] refactor: remove unnecessary CSS/HTML - used Dash-Mantine components styles API - formatted the code via using ruff and black --- assets/tabs.css | 115 +-- pages/explorer.py | 1080 +++++++++++++++++------------ pages/lib/layout.py | 329 +++++---- pages/lib/template_graphs.py | 4 +- pages/lib/utils.py | 113 +-- pages/natural_ventilation.py | 407 +++++------ pages/not_found_404.py | 2 +- pages/outdoor.py | 315 +++++---- pages/psy-chart.py | 381 +++++----- pages/select.py | 95 +-- pages/summary.py | 326 ++++----- pages/sun.py | 113 ++- pages/t_rh.py | 86 ++- pages/wind.py | 504 +++++++------- tests/node/cypress/e2e/spec.cy.js | 2 +- 15 files changed, 2032 insertions(+), 1840 deletions(-) diff --git a/assets/tabs.css b/assets/tabs.css index 4e980f2e..10a44d92 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -287,12 +287,6 @@ p { z-index: 1000; } -/* side bar */ -#sidebar { - background-color: #f8f9fa; - border-right: 1px solid #e9ecef; -} - /* SegmentedControl adapt to container width */ #sidebar .mantine-SegmentedControl-root { width: 100% !important; @@ -303,116 +297,9 @@ p { min-width: 0 !important; } -/* burger button */ -#burger-button { - transition: all 0.2s ease; -} - -#burger-button:hover { - transform: scale(1.05); -} - -.nav-link { - border-radius: 6px; - transition: all 0.2s ease; -} - -.nav-link:hover { - background-color: #e3f2fd; -} - -/* Active navigation link styles */ -.nav-link[data-active="true"] { - background-color: #1976d2 !important; - color: white !important; - font-weight: 600; -} - -.nav-link[data-active="true"]:hover { - background-color: #1565c0 !important; - color: white !important; -} - -/* response design */ +/* Response design */ @media (max-width: 768px) { #sidebar { width: 280px !important; } -} - -/* burger button style */ -#burger-button { - box-shadow: 0 4px 8px rgba(0,0,0,0.1); -} - -/* Alert style */ -.survey-alert { - position: fixed; - top: 25px; - right: 10px; - width: 400px; -} - -/* Footer style */ -#footer-container { - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: nowrap; - width: 100%; - min-height: auto; - padding: 10px 0; -} - -/* Footer Logo style */ -.footer-logo-section { - flex: 0 0 33.333333%; - max-width: 33.333333%; - padding: 10px 15px 10px 25px; - display: flex; - align-items: center; - justify-content: flex-start; -} - -/* Footer style */ -.footer-content-section { - flex: 0 0 66.666667%; - max-width: 66.666667%; - padding: 10px 15px; - display: flex; - align-items: center; - justify-content: flex-start; -} - -/* Footer text style */ -.footer-text-content { - margin-top: 1rem; -} - -/* Footer Markdown style */ -.footer-markdown-text { - font-size: 16px; - line-height: 1.5; - font-weight: 500; -} - -/* Footer link style */ -.footer-links-group { - margin-top: 1rem; -} - -.footer-link { - text-decoration: underline; - font-size: 16px; - font-weight: 500; -} - -/* Banner style */ -#banner-title { - line-height: 1.1; -} - -/* Documentation style */ -#nav-doc-link { - margin-top: 5px; } \ No newline at end of file diff --git a/pages/explorer.py b/pages/explorer.py index 8c27421d..68898ee3 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -1,7 +1,6 @@ import dash -from dash import dcc, html +from dash import dcc import dash_mantine_components as dmc -import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback from dash.exceptions import PreventUpdate @@ -23,8 +22,6 @@ sun_cloud_tab_dropdown_names, more_variables_dropdown, sun_cloud_tab_explore_dropdown_names, - container_row_center_full, - container_col_center_one_of_three, ) from pages.lib.template_graphs import ( heatmap, @@ -65,10 +62,14 @@ def section_one_inputs(): """Return the inputs from section one.""" - return dmc.Box( - className="container-row full-width row-center", + return dmc.Group( + align="center", + justify="center", + gap="sm", + wrap=False, + w="100%", children=[ - html.H4(className="text-next-to-input", children=["Select a variable: "]), + dmc.Title("Select a variable:", order=4), dropdown( id=ElementIds.SEC1_VAR_DROPDOWN, options=explore_dropdown_names, @@ -80,131 +81,157 @@ def section_one_inputs(): def section_one(): """Return the graphs for section one""" - return dmc.Box( - className="container-col full-width", + return dmc.Stack( + w="100%", + gap="md", children=[ section_one_inputs(), - dmc.Box( - children=title_with_link( - text="Yearly chart", - id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), + # Yearly chart + title_with_link( + text="Yearly chart", + id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.YEARLY_EXPLORE, className="full-width"), - ), - dmc.Box( - children=title_with_link( - text="Daily chart", - id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + children=dmc.Paper( + id=ElementIds.YEARLY_EXPLORE, + p="sm", + radius="md", + w="100%", ), ), + # Daily chart + title_with_link( + text="Daily chart", + id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + ), dcc.Loading( - dmc.Box(className="full-width", id=ElementIds.QUERY_DAILY), type="circle", - ), - dmc.Box( - children=title_with_link( - text="Heatmap chart", - id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + children=dmc.Paper( + id=ElementIds.QUERY_DAILY, + p="sm", + radius="md", + w="100%", ), ), + # Heatmap chart + title_with_link( + text="Heatmap chart", + id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + ), dcc.Loading( - dmc.Box(className="full-width", id=ElementIds.QUERY_HEATMAP), type="circle", - ), - dmc.Box( - children=title_with_tooltip( - text="Descriptive statistics", - tooltip_text="count, mean, std, min, max, and percentiles", - id_button=IdButtons.TABLE_EXPLORE, + children=dmc.Paper( + id=ElementIds.QUERY_HEATMAP, + p="sm", + radius="md", + w="100%", ), ), - dmc.Box( - className="container-row justify-content-center", - children=[ - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dbc.Button( - "Apply month and hour filter", - color="primary", - id=ElementIds.SEC1_TIME_FILTER_INPUT, - className="mb-2", - n_clicks=0, - ), - dmc.Box( - className=( - "container-row full-width justify-center mt-2" + title_with_tooltip( + text="Descriptive statistics", + tooltip_text="count, mean, std, min, max, and percentiles", + id_button=IdButtons.TABLE_EXPLORE, + ), + dmc.Center( + w="100%", + children=dmc.Stack( + gap="sm", + w="100%", + maw=600, + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.SEC1_TIME_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + radius="md", + ), + # Month Range 行(3-6-3) + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=3, + children=dmc.Text("Month Range"), ), - children=[ - html.H6("Month Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.SEC1_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - style={"flex": "50%"}, + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.SEC1_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, ), - dcc.Checklist( + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, options=[ - {"label": "Invert", "value": "invert"}, + {"label": "Invert", "value": "invert"} ], value=[], - id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, - labelStyle={"flex": "30%"}, ), - ], - ), - dmc.Box( - className="container-row justify-center", - children=[ - html.H6("Hour Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.SEC1_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, - ), - style={"flex": "50%"}, + ), + ], + ), + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=3, + children=dmc.Text("Hour Range"), + ), + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.SEC1_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, ), - dcc.Checklist( + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, options=[ - {"label": "Invert", "value": "invert"}, + {"label": "Invert", "value": "invert"} ], value=[], - id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, - labelStyle={"flex": "30%"}, ), - ], - ), - ], - ), - ], + ), + ], + ), + ], + ), ), - dmc.Box( + dmc.Paper( id=ElementIds.TABLE_DATA_EXPLORER, + p="sm", + radius="md", + w="100%", ), ], ) @@ -212,170 +239,221 @@ def section_one(): def section_two_inputs(): """Return all the input forms from section two.""" - return dmc.Box( + return dmc.Stack( + w="100%", + gap="md", children=[ - dmc.Box( - children=title_with_tooltip( - text="Customizable heatmap", - tooltip_text=None, - id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL, - ), + # 标题(保留你已有的封装) + title_with_tooltip( + text="Customizable heatmap", + tooltip_text=None, + id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL, ), - dmc.Box( - className="container-row full-width three-inputs-container", + # 三列区域:①变量选择 ②时间过滤(月份/小时)③数据过滤(变量/最小/最大) + dmc.Grid( + gutter="md", children=[ - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - children=["Variable:"], - style={"flex": "30%"}, - ), - dropdown( - id=ElementIds.SEC2_VAR_DROPDOWN, - options=explore_dropdown_names, - value=ColNames.RH, - style={"flex": "70%"}, - ), - ], - ), - ], + # ① 变量选择列 + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="sm", + children=[ + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, + children=dmc.Text("Variable:"), + ), + dmc.GridCol( + span=8, + children=dropdown( + id=ElementIds.SEC2_VAR_DROPDOWN, + options=explore_dropdown_names, + value=ColNames.RH, + ), + ), + ], + ), + ], + ), ), - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dbc.Button( - "Apply month and hour filter", - color="primary", - id=ElementIds.SEC2_TIME_FILTER_INPUT, - className="mb-2", - n_clicks=0, - ), - dmc.Box( - className=( - "container-row full-width justify-center mt-2" + # ② 时间过滤列(按钮 + 月份范围 + 小时范围) + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="sm", + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.SEC2_TIME_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + radius="md", ), - children=[ - html.H6("Month Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.SEC2_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, - labelStyle={"flex": "30%"}, - ), - ], - ), - dmc.Box( - className="container-row justify-center", - children=[ - html.H6("Hour Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.SEC2_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, - ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, - labelStyle={"flex": "30%"}, - ), - ], - ), - ], + # Month Range:3/6/3 + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=3, + children=dmc.Text("Month Range"), + ), + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.SEC2_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, + ), + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, + options=[ + { + "label": "Invert", + "value": "invert", + } + ], + value=[], + ), + ), + ], + ), + # Hour Range:3/6/3 + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=3, + children=dmc.Text("Hour Range"), + ), + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.SEC2_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, + ), + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, + options=[ + { + "label": "Invert", + "value": "invert", + } + ], + value=[], + ), + ), + ], + ), + ], + ), ), - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dbc.Button( - "Apply filter", - color="primary", - id=ElementIds.SEC2_DATA_FILTER_INPUT, - className="mb-2", - n_clicks=0, - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - children=["Filter Variable:"], - style={"flex": "30%"}, - ), - dropdown( - id=ElementIds.SEC2_DATA_FILTER_VAR, - options=explore_dropdown_names, - value=ColNames.RH, - style={"flex": "70%"}, - ), - ], - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - children=["Min Value:"], style={"flex": "30%"} - ), - dbc.Input( - id=ElementIds.SEC2_MIN_VAL, - placeholder="Enter a number for the min val", - type="number", - value=0, - step=1, - style={"flex": "70%"}, - ), - ], - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - children=["Max Value:"], style={"flex": "30%"} - ), - dbc.Input( - id=ElementIds.SEC2_MAX_VAL, - placeholder="Enter a number for the max val", - type="number", - value=100, - step=1, - style={"flex": "70%"}, - ), - ], - ), - ], + # ③ 数据过滤列(按钮 + 过滤变量 + 最小/最大值) + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="sm", + children=[ + dmc.Button( + "Apply filter", + id=ElementIds.SEC2_DATA_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + radius="md", + ), + # Filter Variable + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, + children=dmc.Text("Filter Variable:"), + ), + dmc.GridCol( + span=8, + children=dropdown( + id=ElementIds.SEC2_DATA_FILTER_VAR, + options=explore_dropdown_names, + value=ColNames.RH, + ), + ), + ], + ), + # Min Value + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, + children=dmc.Text("Min Value:"), + ), + dmc.GridCol( + span=8, + children=dmc.NumberInput( + id=ElementIds.SEC2_MIN_VAL, + placeholder="Enter a number for the min val", + value=0, + step=1, + w="100%", + ), + ), + ], + ), + # Max Value + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, + children=dmc.Text("Max Value:"), + ), + dmc.GridCol( + span=8, + children=dmc.NumberInput( + id=ElementIds.SEC2_MAX_VAL, + placeholder="Enter a number for the max val", + value=100, + step=1, + w="100%", + ), + ), + ], + ), + ], + ), ), ], ), @@ -385,31 +463,49 @@ def section_two_inputs(): def section_two(): """Return the two graphs in section two.""" - return dmc.Box( + return dmc.Stack( id=ElementIds.TAB6_SEC2_CONTAINER, - className="container-col justify-center full-width", + w="100%", + gap="md", + align="center", children=[ + # 输入表单 section_two_inputs(), + # 自定义热力图 dcc.Loading( type="circle", - children=dmc.Box(className="full-width", id=ElementIds.CUSTOM_HEATMAP), + children=dmc.Paper( + id=ElementIds.CUSTOM_HEATMAP, + radius="md", + p="sm", + w="100%", + ), ), - dbc.Checklist( - options=[ - {"label": "Normalize", "value": "normal"}, + # Normalize 复选框 + dmc.Group( + gap="sm", + children=[ + dmc.CheckboxGroup( + id=ElementIds.NORMALIZE, + value=[], + children=[ + dmc.Checkbox(label="Normalize", value="normal"), + ], + ), ], - value=[], - id=ElementIds.NORMALIZE, ), + # Summary 图表 dcc.Loading( type="circle", - children=[ - dcc.Graph( - className="full-width", + children=dmc.Paper( + radius="md", + p="sm", + w="100%", + children=dcc.Graph( id=ElementIds.CUSTOM_SUMMARY, config=fig_config, ), - ], + ), ), ], ) @@ -417,175 +513,246 @@ def section_two(): def section_three_inputs(): """""" - return dmc.Box( - className="container-row full-width three-inputs-container", + return dmc.Stack( + w="100%", + gap="md", children=[ - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dmc.Box( - className=container_row_center_full, - children=[ - html.H6(style={"flex": "30%"}, children=["X Variable:"]), - dropdown( - id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, - options=explore_dropdown_names, - value="DBT", - style={"flex": "70%"}, - ), - ], - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6(style={"flex": "30%"}, children=["Y Variable:"]), - dropdown( - id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, - options=explore_dropdown_names, - value=ColNames.RH, - style={"flex": "70%"}, - ), - ], - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6(style={"flex": "30%"}, children=["Color By:"]), - dropdown( - id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, - options=explore_dropdown_names, - value="glob_hor_rad", - style={"flex": "70%"}, - ), - ], - ), - ], - ), - dmc.Box( - className=container_col_center_one_of_three, + dmc.Grid( + gutter="md", children=[ - dbc.Button( - "Apply month and hour filter", - color="primary", - id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, - className="mb-2", - n_clicks=0, - ), - dmc.Box( - className="container-row full-width justify-center", - children=[ - html.H6("Month Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, + # ① X/Y/Color By 列 + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="sm", + children=[ + # X Variable + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, children=dmc.Text("X Variable:") + ), + dmc.GridCol( + span=8, + children=dropdown( + id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, + options=explore_dropdown_names, + value="DBT", + ), + ), + ], ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, - labelStyle={"flex": "30%"}, - ), - ], - ), - dmc.Box( - className="container-row full-width justify-center", - children=[ - html.H6("Hour Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, + # Y Variable + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, children=dmc.Text("Y Variable:") + ), + dmc.GridCol( + span=8, + children=dropdown( + id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, + options=explore_dropdown_names, + value=ColNames.RH, + ), + ), + ], ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, - labelStyle={"flex": "30%"}, - ), - ], - ), - ], - ), - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dbc.Button( - "Apply filter", - color="primary", - id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, - className="mb-2", - n_clicks=0, - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - children=["Filter Variable:"], style={"flex": "30%"} - ), - dropdown( - id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, - options=explore_dropdown_names, - value=ColNames.RH, - style={"flex": "70%"}, - ), - ], + # Color By + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, children=dmc.Text("Color By:") + ), + dmc.GridCol( + span=8, + children=dropdown( + id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, + options=explore_dropdown_names, + value="glob_hor_rad", + ), + ), + ], + ), + ], + ), ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6(children=["Min Value:"], style={"flex": "30%"}), - dbc.Input( - className="num-input", - id=ElementIds.TAB6_SEC3_MIN_VAL, - placeholder="Enter a number for the min val", - type="number", - step=1, - value=0, - style={"flex": "70%"}, - ), - ], + # ② 时间过滤(月/小时) + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="sm", + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + radius="md", + ), + # Month Range:3/6/3 + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=3, children=dmc.Text("Month Range") + ), + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, + ), + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, + options=[ + { + "label": "Invert", + "value": "invert", + } + ], + value=[], + ), + ), + ], + ), + # Hour Range:3/6/3 + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=3, children=dmc.Text("Hour Range") + ), + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, + ), + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, + options=[ + { + "label": "Invert", + "value": "invert", + } + ], + value=[], + ), + ), + ], + ), + ], + ), ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6(children=["Max Value:"], style={"flex": "30%"}), - dbc.Input( - className="num-input", - id=ElementIds.TAB6_SEC3_MAX_VAL, - placeholder="Enter a number for the max val", - type="number", - value=100, - step=1, - style={"flex": "70%"}, - ), - ], + # ③ 数据过滤(变量/最小/最大) + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="sm", + children=[ + dmc.Button( + "Apply filter", + id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + radius="md", + ), + # Filter Variable + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, + children=dmc.Text("Filter Variable:"), + ), + dmc.GridCol( + span=8, + children=dropdown( + id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, + options=explore_dropdown_names, + value=ColNames.RH, + ), + ), + ], + ), + # Min Value + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, children=dmc.Text("Min Value:") + ), + dmc.GridCol( + span=8, + children=dmc.NumberInput( + id=ElementIds.TAB6_SEC3_MIN_VAL, + placeholder="Enter a number for the min val", + value=0, + step=1, + w="100%", + ), + ), + ], + ), + # Max Value + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, children=dmc.Text("Max Value:") + ), + dmc.GridCol( + span=8, + children=dmc.NumberInput( + id=ElementIds.TAB6_SEC3_MAX_VAL, + placeholder="Enter a number for the max val", + value=100, + step=1, + w="100%", + ), + ), + ], + ), + ], + ), ), ], ), @@ -595,24 +762,37 @@ def section_three_inputs(): def section_three(): """Return the two graphs in section three.""" - return dmc.Box( - className="container-col full-width", + return dmc.Stack( + w="100%", + gap="md", children=[ - dmc.Box( - children=title_with_tooltip( - text="More charts", - tooltip_text=None, - id_button=IdButtons.MORE_CHARTS_LABEL, - ), + # 标题(保留你现有的封装) + title_with_tooltip( + text="More charts", + tooltip_text=None, + id_button=IdButtons.MORE_CHARTS_LABEL, ), + # 输入区 section_three_inputs(), + # 图 1:THREE_VAR dcc.Loading( - dmc.Box(id=ElementIds.THREE_VAR), type="circle", + children=dmc.Paper( + id=ElementIds.THREE_VAR, + radius="md", + p="sm", + w="100%", + ), ), + # 图 2:TWO_VAR dcc.Loading( - dmc.Box(id=ElementIds.TWO_VAR), type="circle", + children=dmc.Paper( + id=ElementIds.TWO_VAR, + radius="md", + p="sm", + w="100%", + ), ), ], ) @@ -644,7 +824,7 @@ def update_tab_yearly(_, var, global_local, df, meta, si_ip): """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" if df[var].mean() == 99990.0: - return dbc.Alert( + return dmc.Alert( """The selected variable is not available, the Clima tool could not generate the yearly plot""", color="warning", @@ -782,7 +962,7 @@ def update_heatmap( if not heat_map: return ( - dbc.Alert( + dmc.Alert( "No data is available in this location under these conditions. Please " "either change the month and hour filters, or select a wider range for " "the filter variable", @@ -903,7 +1083,7 @@ def update_more_charts( if not three: custom_inputs = f"{var_x}-{var_y}" units = generate_units(si_ip) - return dbc.Alert( + return dmc.Alert( "No data is available in this location under these conditions. Please " "either change the month and hour filters, or select a wider range for " "the filter variable", diff --git a/pages/lib/layout.py b/pages/lib/layout.py index c1fec70f..a5c61791 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -1,4 +1,3 @@ -import dash_bootstrap_components as dbc import dash from dash import dcc, Input, Output, State, callback import dash_mantine_components as dmc @@ -6,6 +5,7 @@ from pages.lib.global_column_names import ColNames from config import DocLinks, UnitSystem from pages.lib.global_element_ids import ElementIds +from pages.lib.page_icon import PageIcon def burger_button(): @@ -24,30 +24,34 @@ def alert(): return dmc.Stack( gap=0, children=[ - dbc.Toast( + dmc.Alert( [ "If you have a moment, help us improve Clima and take a ", dmc.Anchor( "quick user survey", href="https://forms.gle/k289zP3R92jdu14M7", target="_blank", - className="alert-link", + c="white", + underline=True, ), "! ☀️", ], id=ElementIds.ID_LAYOUT_ALERT_AUTO, - header="CBE Clima User Survey", - icon="info", - is_open=False, - dismissable=True, - className="survey-alert", + title="CBE Clima User Survey", + icon="Info", + color="blue", + variant="filled", + withCloseButton=True, + pos="fixed", + top="25px", + right="10px", + w="400px", + style={"zIndex": 1002, "display": "none"}, ), - dmc.Box( - children=dcc.Interval( - id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, - interval=12 * 1000, - n_intervals=0, - ) + dcc.Interval( + id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, + interval=12 * 500, + n_intervals=0, ), ], ) @@ -55,9 +59,50 @@ def alert(): def footer(): """Build the footer at the bottom of the page.""" + white_anchor_style = { + "underline": True, + "c": "white", + "fz": "md", + "fw": 500, + "target": "_blank", + } + + footer_links = [ + ( + "Version: 0.9.0", + "https://center-for-the-built-environment.gitbook.io/clima/version/changelog", + ), + ("Contributors", "https://cbe-berkeley.gitbook.io/clima/#contributions"), + ( + "Report issues on GitHub", + "https://github.com/CenterForTheBuiltEnvironment/clima/issues", + ), + ( + "Contact us", + "https://github.com/CenterForTheBuiltEnvironment/clima/discussions", + ), + ("Documentation", "https://center-for-the-built-environment.gitbook.io/clima/"), + ( + "License", + "https://center-for-the-built-environment.gitbook.io/clima/#license", + ), + ] + return dmc.Box( id=ElementIds.FOOTER_CONTAINER, + p="md", + m=0, + c="white", + bg="#003262", + display="flex", + w="100%", + style={ + "flexWrap": "nowrap", + "minHeight": "fit-content", + "alignItems": "flex-start", + }, children=[ + # Logo section dmc.Box( children=[ dmc.Anchor( @@ -71,8 +116,13 @@ def footer(): ), ), ], - className="footer-logo-section", + flex="0 0 33.333333%", + maw="33.333333%", + p="30px 15px 10px 25px", + display="flex", + style={"justifyContent": "flex-start", "alignItems": "flex-start"}, ), + # Content section dmc.Box( children=[ dmc.Stack( @@ -85,67 +135,30 @@ def footer(): A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). """, - className="footer-markdown-text", + style={ + "fontSize": "16px", + "lineHeight": 1.5, + "fontWeight": 500, + "color": "white", + }, ), dmc.Group( [ - dmc.Anchor( - "Version: 0.9.0", - href="https://center-for-the-built-environment.gitbook.io/clima/version/changelog", - underline=True, - c="white", - target="_blank", - className="footer-link", - ), - dmc.Anchor( - "Contributors", - href="https://cbe-berkeley.gitbook.io/clima/#contributions", - underline=True, - c="white", - target="_blank", - className="footer-link", - ), - dmc.Anchor( - "Report issues on GitHub", - href="https://github.com/CenterForTheBuiltEnvironment/clima/issues", - underline=True, - c="white", - target="_blank", - className="footer-link", - ), - dmc.Anchor( - "Contact us", - href="https://github.com/CenterForTheBuiltEnvironment/clima/discussions", - underline=True, - c="white", - target="_blank", - className="footer-link", - ), - dmc.Anchor( - "Documentation", - href="https://center-for-the-built-environment.gitbook.io/clima/", - underline=True, - c="white", - target="_blank", - className="footer-link", - ), - dmc.Anchor( - "License", - href="https://center-for-the-built-environment.gitbook.io/clima/#license", - underline=True, - c="white", - target="_blank", - className="footer-link", - ), + dmc.Anchor(text, href=url, **white_anchor_style) + for text, url in footer_links ], gap="sm", - className="footer-links-group", + mt="md", ), ], - className="footer-text-content", + mt="md", ), ], - className="footer-content-section", + flex="0 0 66.666667%", + maw="66.666667%", + p="0px 15px 10px 15px", + display="flex", + style={"justifyContent": "flex-start", "alignItems": "flex-start"}, ), ], ) @@ -155,6 +168,11 @@ def banner(): """Top banner rewritten with dash-mantine-components only.""" return dmc.Box( id=ElementIds.BANNER, + p="md", + bg="#003262", + c="white", + pos="relative", + style={"zIndex": 1}, children=[ dmc.Group( justify="space-between", @@ -176,12 +194,21 @@ def banner(): "CBE Clima Tool", id=ElementIds.BANNER_TITLE, order=2, + fw=500, + ff="'Open Sans', sans-serif", + lh=1.1, + c="white", ), dmc.Text( "Current Location: N/A", id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE, size="sm", opacity=0.85, + ff="'Poppins', sans-serif", + fw=400, + h=25, + style={"overflow": "hidden"}, + c="white", ), ], ), @@ -194,7 +221,7 @@ def banner(): def sidebar(): - """create side bar""" + """create sidebar""" return dmc.Drawer( id=ElementIds.SIDE_BAR, title=dmc.Group( @@ -203,50 +230,87 @@ def sidebar(): dmc.Text("CBE Clima Tool", fw=600), ] ), - padding="md", size="300px", - zIndex=999, + zIndex=1001, opened=False, - className="custom-sidebar", styles={ - "title": {"paddingRight": 30}, + "content": { + "top": "80px", + "left": 0, + "position": "fixed", + "borderRadius": "0 8px 8px 0", + "boxShadow": "2px 0 8px rgba(0,0,0,0.1)", + "backgroundColor": "#f8f9fa", + "padding": "16px", + }, + "overlay": { + "top": "80px", + "left": 0, + "height": "calc(100vh - 80px)", + "position": "fixed", + }, + "header": { + "borderBottom": "1px solid #e9ecef", + "paddingBottom": "12px", + "marginBottom": "16px", + "position": "sticky", + "top": 0, + "backgroundColor": "#f8f9fa", + "zIndex": 1002, + }, + "title": { + "fontWeight": 600, + "fontSize": "18px", + "paddingRight": 30, + "position": "relative", + "zIndex": 1001, + }, + "body": { + "padding": 0, + "overflowY": "auto", + "maxHeight": "calc(100vh - 80px)", + "position": "relative", + "zIndex": 1, + }, }, - children=[dmc.Stack(gap="sm", children=build_sidebar_nav_items())], + children=[dmc.Stack(gap=0, children=build_sidebar_nav_items())], ) -# Pages Icon -PAGE_ICON_MAP = { - "Select Weather File": "tabler:upload", - "Climate Summary": "tabler:chart-bar", - "Temperature and Humidity": "tabler:temperature", - "Sun and Clouds": "tabler:sun", - "Wind": "tabler:wind", - "Psychrometric Chart": "tabler:chart-dots", - "Natural Ventilation": "tabler:windmill", - "Outdoor Comfort": "tabler:thermometer", - "Data Explorer": "tabler:database", - "Changelog": "tabler:history", -} - - def build_sidebar_nav_items(): + nav_link_styles = { + "root": { + "borderRadius": "6px", + "transition": "all 0.2s ease", + "&:hover": {"backgroundColor": "#e3f2fd"}, + "&[data-active='true']": { + "backgroundColor": "#1976d2", + "color": "white", + "fontWeight": 600, + }, + "&[data-active='true']:hover": { + "backgroundColor": "#1565c0", + "color": "white", + }, + } + } + # === Secondary Menu === - sub_links = [] - for page in dash.page_registry.values(): - if page[ColNames.NAME] in ["404"]: - continue - icon = PAGE_ICON_MAP.get(page[ColNames.NAME], "tabler:circle") - sub_links.append( - dmc.NavLink( - label=page[ColNames.NAME], - leftSection=DashIconify(icon=icon, width=20), - href=page[ColNames.PATH], - id=f"nav-{page[ColNames.PATH].replace('/', '')}", - active=False, - style={"marginBottom": "4px"}, - ) + sub_links = [ + dmc.NavLink( + label=page[ColNames.NAME], + leftSection=DashIconify( + icon=PageIcon.get_icon(page[ColNames.NAME]), width=20 + ), + href=page[ColNames.PATH], + id=f"nav-{page[ColNames.PATH].replace('/', '')}", + active=False, + mb="xs", + styles=nav_link_styles, ) + for page in dash.page_registry.values() + if page[ColNames.NAME] not in ["404"] + ] # Primary Menu parent_group = dmc.NavLink( @@ -258,6 +322,11 @@ def build_sidebar_nav_items(): childrenOffset=18, ) + segmented_control_styles = { + "root": {"width": "100%"}, + "control": {"flex": 1, "minWidth": 0}, + } + controls_stack = dmc.Stack( gap="sm", px=0, @@ -272,6 +341,8 @@ def build_sidebar_nav_items(): ], radius="md", size="sm", + w="100%", + styles=segmented_control_styles, ), dmc.SegmentedControl( id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT, @@ -282,6 +353,8 @@ def build_sidebar_nav_items(): ], radius="md", size="sm", + w="100%", + styles=segmented_control_styles, ), ], ) @@ -324,18 +397,16 @@ def store(): def build_tabs(): return dmc.Box( id=ElementIds.TABS_CONTAINER, + m=0, + mt=0, children=[ + store(), dmc.Box( - id=ElementIds.STORE_CONTAINER, + id=ElementIds.TABS_CONTENT, + p="md", children=[ - store(), - dmc.Box( - id=ElementIds.TABS_CONTENT, - children=[ - alert(), - dash.page_container, - ], - ), + alert(), + dash.page_container, ], ), ], @@ -349,9 +420,7 @@ def build_tabs(): prevent_initial_call=True, ) def toggle_sidebar(n_clicks, opened): - if n_clicks: - return not opened - return opened + return not opened if n_clicks else opened @callback( @@ -375,14 +444,30 @@ def close_sidebar_on_navigation(pathname): ) def update_nav_active_state(pathname): """Update active state of navigation links based on current URL pathname""" - active_states = [] + return [ + pathname == page[ColNames.PATH] + for page in dash.page_registry.values() + if page[ColNames.NAME] not in ["404"] + ] - for page in dash.page_registry.values(): - if page[ColNames.NAME] in ["404"]: - continue - # Check if current pathname matches this page's path - is_active = pathname == page[ColNames.PATH] - active_states.append(is_active) +@callback( + Output(ElementIds.ID_LAYOUT_ALERT_AUTO, "style"), + Input(ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, "n_intervals"), + prevent_initial_call=True, +) +def show_alert_after_delay(n_intervals): + """Show alert after 6 seconds, then hide after 5 more seconds""" + base_style = { + "position": "fixed", + "top": "25px", + "right": "10px", + "width": "400px", + "zIndex": 1002, + } - return active_states + # Determine display status based on the number of intervals + if n_intervals == 1: + return {**base_style, "display": "block"} + else: + return {**base_style, "display": "none"} diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index f8e36418..b62f199b 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -642,7 +642,7 @@ def thermal_stress_stacked_barchart( style={"text-align": "center", "marginTop": "2rem"}, ), ) - isNormalized = True if len(normalize) != 0 else False + isNormalized = True if normalize else False if isNormalized: new_df = ( df.groupby(ColNames.MONTH)[var] @@ -674,7 +674,7 @@ def thermal_stress_stacked_barchart( "

Month: %{x}
Category: " + categories[i] + "
Count: %{y}
" - if len(normalize) == 0 + if not normalize else "

Month: %{x}
Category: " + categories[i] + "
Proportion: %{y:.1f}%
" diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 4014db9a..dc20810a 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -3,9 +3,8 @@ import time import math -import dash_bootstrap_components as dbc import pandas as pd -from dash import html, dash_table, dcc +from dash import dash_table, dcc import dash_mantine_components as dmc from config import UnitSystem @@ -148,38 +147,44 @@ def generate_custom_inputs_psy( def title_with_tooltip(text, tooltip_text, id_button): - display_tooltip = "none" if tooltip_text: - display_tooltip = "block" - - return dmc.Box( - className="container-row", - style={"padding": "1rem", "marginTop": "1rem"}, - children=[ - html.H5(text, style={"marginRight": "0.5rem"}), - dmc.Box( - [ - html.Sup( - html.Img( + return dmc.Group( + gap="xs", + align="center", + mt="md", + px="md", + children=[ + dmc.Title(text, order=3), + dmc.Tooltip( + label=tooltip_text, + position="right", + withArrow=True, + multiline=True, + w=220, + children=dmc.ActionIcon( + dmc.Image( id=id_button, - src="../assets/icons/help.png", + src="/assets/icons/help.png", alt="help", - style={ - "width": "1rem", - "height": "1rem", - }, + w=16, + h=16, ), + variant="transparent", + size="sm", ), - dbc.Tooltip( - tooltip_text, - target=id_button, - placement="right", - ), - ], - style={"display": display_tooltip}, - ), - ], - ) + ), + ], + ) + else: + return dmc.Group( + gap="xs", + align="center", + mt="md", + px="md", + children=[ + dmc.Title(text, order=3), + ], + ) def title_with_link( @@ -188,34 +193,34 @@ def title_with_link( id_button=None, doc_link: str = "", ): - return dmc.Box( - className="container-row", - style={"padding": "1rem", "marginTop": "1rem"}, + return dmc.Group( + gap="xs", + align="center", + mt="md", + px="md", children=[ - html.H5(text, style={"marginRight": "0.5rem"}), - dmc.Box( - [ - html.Sup( - html.A( - html.Img( - id=id_button, - src="../assets/icons/book.png", - alt="book", - style={ - "width": "1rem", - "height": "1rem", - }, - ), - href=doc_link, - target="_blank", + dmc.Title(text, order=3), + dmc.Tooltip( + label=tooltip_text, + position="right", + withArrow=True, + multiline=True, + w=220, + children=dmc.Anchor( + dmc.ActionIcon( + dmc.Image( + id=id_button, + src="/assets/icons/book.png", + alt="book", + w=16, + h=16, ), + variant="transparent", + size="sm", ), - dbc.Tooltip( - tooltip_text, - target=id_button, - placement="right", - ), - ], + href=doc_link, + target="_blank", + ), ), ], ) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index bf1bc88f..ea6835fd 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,5 +1,5 @@ import dash -from dash import dcc, html +from dash import dcc import dash_mantine_components as dmc import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback @@ -13,8 +13,6 @@ mapping_dictionary, tight_margins, month_lst, - container_row_center_full, - container_col_center_one_of_three, ) from pages.lib.utils import get_max_min_value from pages.lib.template_graphs import filter_df_by_month_and_hour @@ -42,13 +40,7 @@ def layout(): - return dmc.Box( - className="container-col", - id=ElementIds.MAIN_NV_SECTION, - children=[ - # - ], - ) + return dmc.Stack(id=ElementIds.MAIN_NV_SECTION, gap="md") @callback( @@ -66,220 +58,239 @@ def update_layout(si_ip): dpt_set = 16 return [ - dmc.Box( - children=title_with_link( - text="Natural Ventilation Potential", - id_button=IdButtons.NATURAL_VENTILATION_LABEL, - doc_link=DocLinks.NATURAL_VENTILATION, - ), + title_with_link( + text="Natural Ventilation Potential", + id_button=IdButtons.NATURAL_VENTILATION_LABEL, + doc_link=DocLinks.NATURAL_VENTILATION, ), inputs_tab(tdb_set_min, tdb_set_max, dpt_set), dcc.Loading( - dmc.Box( + type="circle", + children=dmc.Paper( id=ElementIds.NV_HEATMAP_CHART, - style={"marginTop": "1rem"}, + p="md", + mt="md", ), - type="circle", ), - dmc.Box( - className="container-row align-center justify-center", + dmc.Group( + align="center", + justify="center", + gap="sm", children=[ - dbc.Checklist( - options=[ - {"label": "", "value": 1}, - ], - value=[1], + dmc.Switch( id=ElementIds.SWITCHES_INPUT, - switch=True, - style={ - "padding": "1rem", - "marginTop": "1rem", - "marginRight": "-2rem", - }, + label="", + checked=True, + size="md", + color="blue", + style={"padding": "1rem", "marginRight": "-2rem"}, ), - dmc.Box( - children=title_with_tooltip( - text="Normalize data", - tooltip_text=( - "If normalized is enabled it calculates the % " - "time otherwise it calculates the total number of hours" - ), - id_button=IdButtons.NV_NORMALIZE, + title_with_tooltip( + text="Normalize data", + tooltip_text=( + "If normalized is enabled it calculates the % " + "time otherwise it calculates the total number of hours" ), + id_button=IdButtons.NV_NORMALIZE, ), ], ), dcc.Loading( - dmc.Box( + type="circle", + children=dmc.Paper( id=ElementIds.NV_BAR_CHART, - style={"marginTop": "1rem"}, + p="md", + mt="md", ), - type="circle", ), ] def inputs_tab(t_min, t_max, d_set): - return dmc.Box( - className="container-row full-width three-inputs-container", + return dmc.Grid( + gutter="xl", children=[ - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dbc.Button( - "Apply filter", - color="primary", - id=ElementIds.NV_DBT_FILTER, - className="mb-2", - n_clicks=1, - ), - html.H6("Outdoor dry-bulb air temperature range"), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6(children=["Min Value:"], style={"flex": "30%"}), - dbc.Input( - id=ElementIds.NV_TDB_MIN_VAL, - placeholder="Enter a number for the min val", - type="number", - step=1, - value=t_min, - style={"flex": "70%"}, - ), - ], - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6(children=["Max Value:"], style={"flex": "30%"}), - dbc.Input( - id=ElementIds.NV_TDB_MAX_VAL, - placeholder="Enter a number for the max val", - type="number", - value=t_max, - step=1, - style={"flex": "70%"}, - ), - ], - ), - ], - ), - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dbc.Button( - "Apply month and hour filter", - color="primary", - id=ElementIds.NV_MONTH_HOUR_FILTER, - className="mb-2", - n_clicks=0, - ), - dmc.Box( - className="container-row full-width justify-center mt-2", - children=[ - html.H6("Month Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.NV_MONTH_SLIDER, - min=1, - max=12, + dmc.GridCol( + span=4, + children=dmc.Stack( + children=[ + dmc.Button( + "Apply filter", + color="primary", + id=ElementIds.NV_DBT_FILTER, + variant="link", + size="md", + fullWidth=True, + n_clicks=1, + ), + dmc.Text( + "Outdoor dry-bulb air temperature range", + size="md", + ), + dmc.Group( + gap="xl", + grow=True, + children=[ + dmc.Text("Min Value:", size="md"), + dmc.NumberInput( + id=ElementIds.NV_TDB_MIN_VAL, + placeholder="Enter a number for the min val", step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, + value=t_min, ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_MONTH_NV, - labelStyle={"flex": "30%"}, - ), - ], - ), - dmc.Box( - className="container-row align-center justify-center", - children=[ - html.H6("Hour Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.NV_HOUR_SLIDER, - min=0, - max=24, + ], + ), + dmc.Group( + gap="xl", + grow=True, + children=[ + dmc.Text("Max Value:", size="md"), + dmc.NumberInput( + id=ElementIds.NV_TDB_MAX_VAL, + placeholder="Enter a number for the max val", + value=t_max, step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_HOUR_NV, - labelStyle={"flex": "30%"}, - ), - ], - ), - ], + ], + ), + ], + ), ), - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dbc.Button( - "Apply filter", - color="primary", - id=ElementIds.NV_DPT_FILTER, - className="mb-2", - n_clicks=0, - disabled=True, - ), - dbc.Checklist( - options=[ - { - "label": ( - "Avoid condensation with radiant systems: If the" - " outdoor dew point temperature is below the" - " radiant system surface temperature, the data" - " point is not plot." + dmc.GridCol( + span=4, + children=dmc.Stack( + children=[ + dmc.Button( + "Apply month and hour filter", + color="primary", + id=ElementIds.NV_MONTH_HOUR_FILTER, + variant="link", + size="md", + fullWidth=True, + radius="sm", + ), + dmc.Grid( + align="center", + gutter="sm", + children=[ + dmc.GridCol( + span=3, + children=dmc.Text("Month Range", size="md"), ), - "value": 1, - }, - ], - value=[], - id=ElementIds.ENABLE_CONDENSATION, - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - children=["Surface temperature:"], - style={"marginRight": "1rem"}, - ), - dbc.Input( - id=ElementIds.NV_DPT_MAX_VAL, - placeholder="Enter a number for the max val", - type="number", - value=d_set, - step=1, - style={"flex": "1"}, - ), - ], - ), - ], + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.NV_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, + ), + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + options=[ + {"label": "Invert", "value": "invert"}, + ], + value=[], + id=ElementIds.INVERT_MONTH_NV, + ), + ), + ], + ), + dmc.Grid( + align="center", + gutter="sm", + children=[ + dmc.GridCol( + span=3, + children=dmc.Text("Hour Range", size="md"), + ), + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.NV_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, + ), + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + options=[ + {"label": "Invert", "value": "invert"}, + ], + value=[], + id=ElementIds.INVERT_HOUR_NV, + ), + ), + ], + ), + ], + ), + ), + dmc.GridCol( + span=4, + children=dmc.Stack( + children=[ + dmc.Button( + "Apply filter", + color="primary", + id=ElementIds.NV_DPT_FILTER, + mb="xs", + variant="link", + size="md", + fullWidth=True, + n_clicks=0, + disabled=True, + ), + dcc.Checklist( + options=[ + { + "label": ( + "Avoid condensation with radiant systems: If the" + " outdoor dew point temperature is below the" + " radiant system surface temperature, the data" + " point is not plot." + ), + "value": 1, + }, + ], + value=[], + id=ElementIds.ENABLE_CONDENSATION, + ), + dmc.Group( + align="center", + gap="sm", + grow=True, + children=[ + dmc.Text("Surface temperature:", size="md"), + dmc.NumberInput( + id=ElementIds.NV_DPT_MAX_VAL, + placeholder="Enter a number for the max val", + value=d_set, + step=1, + w="50%", + ), + ], + ), + ], + ), ), ], ) @@ -458,7 +469,7 @@ def nv_heatmap( Input(ElementIds.NV_MONTH_HOUR_FILTER, "n_clicks"), Input(ElementIds.NV_DBT_FILTER, "n_clicks"), Input(ElementIds.NV_DPT_FILTER, "n_clicks"), - Input(ElementIds.SWITCHES_INPUT, "value"), + Input(ElementIds.SWITCHES_INPUT, "checked"), Input(ElementIds.ENABLE_CONDENSATION, "value"), ], [ @@ -539,7 +550,7 @@ def nv_bar_chart( per_time_nv_allowed = np.round(100 * (n_hours_nv_allowed / tot_month_hours)) - if len(normalize) == 0: + if not normalize: fig = go.Figure( go.Bar( x=df[ColNames.MONTH_NAMES].unique(), diff --git a/pages/not_found_404.py b/pages/not_found_404.py index 61e22382..9fc307e5 100644 --- a/pages/not_found_404.py +++ b/pages/not_found_404.py @@ -13,7 +13,7 @@ layout = [ dmc.Title("I could not find the page you are currently looking for", order=4), - dmc.Text("Use the button below to return to the home page.", className="mb-2"), + dmc.Text("Use the button below to return to the home page.", mb="sm"), Lottie( options=dict( loop=True, diff --git a/pages/outdoor.py b/pages/outdoor.py index 7e8335fc..c33c8b5c 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -1,7 +1,6 @@ import dash from dash import dcc, html import dash_mantine_components as dmc -import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback import numpy as np @@ -37,184 +36,220 @@ def inputs_outdoor_comfort(): - return dbc.Row( - className="container-row full-width three-inputs-container", + return dmc.Grid( + gutter="md", children=[ - dbc.Col( - md=6, - sm=12, - children=[ - dmc.Box( - className="container-row center-block", - children=[ - html.H4( - children=["Select a scenario:"], - style={"flex": "30%"}, - ), - dropdown( + dmc.GridCol( + span=6, + children=dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol(span=3, children=dmc.Text("Select a scenario:")), + dmc.GridCol( + span=6, + children=dropdown( id=ElementIds.TAB7_DROPDOWN, - style={"flex": "60%"}, options=outdoor_dropdown_names, value="utci_Sun_Wind", + persistence=True, + persistence_type="session", ), - dmc.Box( - id=ElementIds.IMAGE_SELECTION, style={"flex": "10%"} - ), - ], - ), - ], + ), + dmc.GridCol( + span=3, + children=dmc.Paper(id=ElementIds.IMAGE_SELECTION), + ), + ], + ), ), - dbc.Col( - md=6, - sm=12, - children=[ - dbc.Button( - "Apply month and hour filter", - color="primary", - style={ - "width": "100%", - }, - id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, - className="mb-2", - n_clicks=0, - ), - dmc.Box( - className="container-row full-width justify-center mt-2", - children=[ - html.H6("Month Range", style={"flex": "5%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, + dmc.GridCol( + span=6, + children=dmc.Stack( + gap="sm", + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, + variant="filled", + color="blue", + size="md", + radius="md", + w="100%", + ), + # Month Range + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol(span=2, children=dmc.Text("Month Range")), + dmc.GridCol( + span=7, + children=dcc.RangeSlider( + id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, + ), ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, - labelStyle={"flex": "30%"}, - ), - ], - ), - dmc.Box( - className="container-row align-center justify-center", - children=[ - html.H6("Hour Range", style={"flex": "5%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, + options=[ + {"label": "Invert", "value": "invert"} + ], + value=[], + ), ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, - labelStyle={"flex": "30%"}, - ), - ], - ), - ], + ], + ), + # Hour Range + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol(span=2, children=dmc.Text("Hour Range")), + dmc.GridCol( + span=7, + children=dcc.RangeSlider( + id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, + ), + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, + options=[ + {"label": "Invert", "value": "invert"} + ], + value=[], + ), + ), + ], + ), + ], + ), ), ], ) def outdoor_comfort_chart(): - return dmc.Box( + return dmc.Stack( + w="100%", + gap="md", children=[ - dmc.Box(id=ElementIds.OUTDOOR_COMFORT_OUTPUT), - dmc.Box( - children=title_with_link( - text="UTCI heatmap chart", - id_button=IdButtons.UTCI_CHARTS_LABEL, - doc_link=DocLinks.UTCI_CHART, - ) + # 输出区域 + dmc.Paper( + id=ElementIds.OUTDOOR_COMFORT_OUTPUT, + radius="md", + p="sm", + w="100%", + ), + # UTCI heatmap chart + title_with_link( + text="UTCI heatmap chart", + id_button=IdButtons.UTCI_CHARTS_LABEL, + doc_link=DocLinks.UTCI_CHART, ), dcc.Loading( - dmc.Box(id=ElementIds.UTCI_HEATMAP), type="circle", + children=dmc.Paper( + id=ElementIds.UTCI_HEATMAP, + radius="md", + p="sm", + w="100%", + h=400, + ), ), - dmc.Box( - children=title_with_link( - text="UTCI thermal stress chart", - id_button=IdButtons.UTCI_CHARTS_LABEL, - doc_link=DocLinks.UTCI_CHART, - ) + # UTCI thermal stress chart + title_with_link( + text="UTCI thermal stress chart", + id_button=IdButtons.UTCI_CHARTS_LABEL, + doc_link=DocLinks.UTCI_CHART, ), dcc.Loading( - dmc.Box(id=ElementIds.UTCI_CATEGORY_HEATMAP), type="circle", + children=dmc.Paper( + id=ElementIds.UTCI_CATEGORY_HEATMAP, + radius="md", + p="sm", + w="100%", + h=400, + ), ), - dmc.Box( - className="container-row align-center justify-center", + # Normalize data 开关 + Tooltip + dmc.Group( + align="center", + justify="center", + gap="sm", children=[ - dbc.Checklist( - options=[ - {"label": "", "value": 1}, - ], - value=[1], + dmc.Switch( id=ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, - switch=True, - style={ - "padding": "1rem", - "marginTop": "1rem", - "marginRight": "-2rem", - }, + label="", + checked=True, + size="md", + color="blue", ), - dmc.Box( - children=title_with_tooltip( - text="Normalize data", - tooltip_text=( - "If normalized is enabled it calculates the % " - "time otherwise it calculates the total number of hours" - ), - id_button=IdButtons.OUTDOOR_COMFORT_NORMALIZE, + title_with_tooltip( + text="Normalize data", + tooltip_text=( + "If normalized is enabled it calculates the % time " + "otherwise it calculates the total number of hours" ), + id_button=IdButtons.OUTDOOR_COMFORT_NORMALIZE, ), ], ), + # Summary chart dcc.Loading( - dmc.Box(id=ElementIds.UTCI_SUMMARY_CHART), type="circle", + children=dmc.Paper( + id=ElementIds.UTCI_SUMMARY_CHART, + radius="md", + p="sm", + w="100%", + ), ), ], ) def layout(): - return ( - dcc.Loading( - type="circle", - children=dmc.Box( - className="container-col", - children=[inputs_outdoor_comfort(), outdoor_comfort_chart()], + return dmc.Stack( + w="100%", + gap="md", + children=[ + dcc.Loading( + type="circle", + children=dmc.Stack( + w="100%", + gap="md", + children=[ + inputs_outdoor_comfort(), + outdoor_comfort_chart(), + ], + ), ), - ), + ], ) diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 80b512be..672151dc 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -1,7 +1,6 @@ import dash -from dash import dcc, html +from dash import dcc import dash_mantine_components as dmc -import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback from copy import deepcopy @@ -17,8 +16,6 @@ from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames from pages.lib.global_scheme import ( - container_row_center_full, - container_col_center_one_of_three, dropdown_names, sun_cloud_tab_dropdown_names, more_variables_dropdown, @@ -61,154 +58,211 @@ def inputs(): """""" - return dmc.Box( - className="container-row full-width three-inputs-container", + return dmc.Stack( + w="100%", + gap="md", children=[ - dmc.Box( - className=container_col_center_one_of_three, + dmc.Grid( + gutter="md", children=[ - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - children=["Color By:"], - style={"flex": "30%"}, - ), - dropdown( - id=ElementIds.PSY_COLOR_BY_DROPDOWN, - options=psy_dropdown_names, - value="Frequency", - style={"flex": "70%"}, - persistence_type="session", - persistence=True, - ), - ], - ), - ], - ), - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dbc.Button( - "Apply month and hour filter", - color="primary", - id=ElementIds.MONTH_HOUR_FILTER, - className="mb-2", - n_clicks=0, - ), - dmc.Box( - className="container-row full-width justify-center mt-2", - children=[ - html.H6("Month Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.PSY_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, + # ① Color By + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="sm", + children=[ + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, children=dmc.Text("Color By:") + ), + dmc.GridCol( + span=8, + children=dropdown( + id=ElementIds.PSY_COLOR_BY_DROPDOWN, + options=psy_dropdown_names, + value="Frequency", + persistence=True, + persistence_type="session", + ), + ), + ], ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_MONTH_PSY, - labelStyle={"flex": "30%"}, - ), - ], + ], + ), ), - dmc.Box( - className="container-row align-center justify-center", - children=[ - html.H6("Hour Range", style={"flex": "20%"}), - dmc.Box( - dcc.RangeSlider( - id=ElementIds.PSY_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, + # ② 时间过滤(月/小时) + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="sm", + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.MONTH_HOUR_FILTER, + variant="filled", + color="blue", + size="md", + radius="md", ), - style={"flex": "50%"}, - ), - dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_HOUR_PSY, - labelStyle={"flex": "30%"}, - ), - ], - ), - ], - ), - dmc.Box( - className=container_col_center_one_of_three, - children=[ - dbc.Button( - "Apply filter", - color="primary", - id=ElementIds.DATA_FILTER, - className="mb-2", - n_clicks=0, - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - children=["Filter Variable:"], style={"flex": "30%"} - ), - dropdown( - id=ElementIds.PSY_VAR_DROPDOWN, - options=dropdown_names, - value=ColNames.RH, - style={"flex": "70%"}, - ), - ], - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6(children=["Min Value:"], style={"flex": "30%"}), - dbc.Input( - id=ElementIds.PSY_MIN_VAL, - placeholder="Enter a number for the min val", - type="number", - step=1, - value=0, - style={"flex": "70%"}, - ), - ], + # Month Range:3/6/3 + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=3, children=dmc.Text("Month Range") + ), + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.PSY_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, + ), + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_MONTH_PSY, + options=[ + { + "label": "Invert", + "value": "invert", + } + ], + value=[], + ), + ), + ], + ), + # Hour Range:3/6/3 + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=3, children=dmc.Text("Hour Range") + ), + dmc.GridCol( + span=6, + children=dcc.RangeSlider( + id=ElementIds.PSY_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, + ), + ), + dmc.GridCol( + span=3, + children=dcc.Checklist( + id=ElementIds.INVERT_HOUR_PSY, + options=[ + { + "label": "Invert", + "value": "invert", + } + ], + value=[], + ), + ), + ], + ), + ], + ), ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6(children=["Max Value:"], style={"flex": "30%"}), - dbc.Input( - id=ElementIds.PSY_MAX_VAL, - placeholder="Enter a number for the max val", - type="number", - value=100, - step=1, - style={"flex": "70%"}, - ), - ], + # ③ 数据过滤(变量/最小/最大) + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="sm", + children=[ + dmc.Button( + "Apply filter", + id=ElementIds.DATA_FILTER, + variant="filled", + color="blue", + size="md", + radius="md", + ), + # Filter Variable + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, + children=dmc.Text("Filter Variable:"), + ), + dmc.GridCol( + span=8, + children=dropdown( + id=ElementIds.PSY_VAR_DROPDOWN, + options=dropdown_names, + value=ColNames.RH, + ), + ), + ], + ), + # Min Value + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, children=dmc.Text("Min Value:") + ), + dmc.GridCol( + span=8, + children=dmc.NumberInput( + id=ElementIds.PSY_MIN_VAL, + placeholder="Enter a number for the min val", + value=0, + step=1, + w="100%", + ), + ), + ], + ), + # Max Value + dmc.Grid( + gutter="sm", + align="center", + children=[ + dmc.GridCol( + span=4, children=dmc.Text("Max Value:") + ), + dmc.GridCol( + span=8, + children=dmc.NumberInput( + id=ElementIds.PSY_MAX_VAL, + placeholder="Enter a number for the max val", + value=100, + step=1, + w="100%", + ), + ), + ], + ), + ], + ), ), ], ), @@ -217,21 +271,34 @@ def inputs(): def layout(): - return ( - dmc.Box( - children=title_with_link( + return dmc.Stack( + w="100%", + gap="md", + children=[ + # 标题(保留封装) + title_with_link( text="Psychrometric Chart", id_button=IdButtons.PSYCHROMETRIC_CHART_CHART, doc_link=DocLinks.PSYCHROMETRIC_CHART, ), - ), - dcc.Loading( - type="circle", - children=dmc.Box( - className="container-col", - children=[inputs(), dmc.Box(id=ElementIds.PSYCH_CHART)], + # 内容区:输入区 + 图表 + dcc.Loading( + type="circle", + children=dmc.Stack( + w="100%", + gap="md", + children=[ + inputs(), + dmc.Paper( + id=ElementIds.PSYCH_CHART, + radius="md", + p="sm", + w="100%", + ), + ], + ), ), - ), + ], ) @@ -293,7 +360,7 @@ def update_psych_chart( if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return ( - dbc.Alert( + dmc.Alert( "No data is available in this location under these conditions. Please " "either change the month and hour filters, or select a wider range for " "the filter variable", diff --git a/pages/select.py b/pages/select.py index 330c8728..fefa5872 100644 --- a/pages/select.py +++ b/pages/select.py @@ -40,7 +40,6 @@ def layout(): """Contents in the first tab 'Select Weather File'""" return dmc.Box( - className="container-col tab-container", children=[ dcc.Loading( id=ElementIds.LOADING_ONE, @@ -50,20 +49,21 @@ def layout(): ), dcc.Upload( id=ElementIds.UPLOAD_DATA, - children=dbc.Button( + children=dmc.Button( [ "Drag and Drop or ", html.A("Select an EPW file from your computer"), ], id=ElementIds.UPLOAD_DATA_BUTTON, - outline=True, - color="secondary", - className="mt-2", - style={"borderRadius": "5px", "borderStyle": "dashed"}, + variant="outline", + color="gray", + radius="sm", + style={"borderStyle": "dashed", "borderRadius": "5px"}, + styles={"label": {"fontWeight": 400}}, ), # Allow multiple files to be uploaded multiple=True, - className="d-grid", + style={"display": "grid"}, ), dmc.Skeleton( visible=False, @@ -71,30 +71,53 @@ def layout(): height=500, children=dmc.Box(id=ElementIds.TAB_ONE_MAP), ), - dbc.Modal( - [ - dbc.ModalHeader(id=ElementIds.MODAL_HEADER), - dbc.ModalFooter( - children=[ - dbc.Button( + dmc.Modal( + id=ElementIds.MODAL, + title=dmc.Text(id=ElementIds.MODAL_HEADER), + opened=False, + centered=True, + children=[ + dmc.Divider( + size="xs", + color="gray", + my="sm", + style={ + "borderTop": "1px solid var(--mantine-color-gray-4)", + "marginTop": "-6px", + }, + ), + dmc.Group( + [ + dmc.Button( "Close", id=ElementIds.MODAL_CLOSE_BUTTON, - className="ml-2", - color="light", + ml="sm", + color="gray", + variant="outline", ), - dbc.Button( + dmc.Button( "Yes", id=ElementIds.MODAL_YES_BUTTON, - className="ml-2", - color="primary", + ml="sm", + color="blue", ), - ] + ], + justify="flex-end", + gap="md", + w="100%", ), ], - id=ElementIds.MODAL, - is_open=False, ), ], + w="100%", + mx=0, + px=0, + py="md", + style={ + "display": "flex", + "flexDirection": "column", + "gap": "var(--mantine-spacing-md)", + }, ) @@ -288,7 +311,7 @@ def enable_tabs_when_data_is_loaded(meta, data): @callback( [ - Output(ElementIds.MODAL, "is_open"), + Output(ElementIds.MODAL, "opened"), Output(ElementIds.ID_SELECT_URL_STORE, "data"), ], [ @@ -296,30 +319,25 @@ def enable_tabs_when_data_is_loaded(meta, data): Input(ElementIds.TAB_ONE_MAP, "clickData"), Input(ElementIds.MODAL_CLOSE_BUTTON, "n_clicks"), ], - [State(ElementIds.MODAL, "is_open")], + [State(ElementIds.MODAL, "opened")], prevent_initial_call=True, ) -def display_modal_when_data_clicked(_, click_map, __, is_open): +def display_modal_when_data_clicked(_, click_map, __, opened): """display the modal to the user and check if he wants to use that file""" if click_map: url = re.search( r'href=[\'"]?([^\'" >]+)', click_map["points"][0]["customdata"][-1] ).group(1) - return not is_open, url - return is_open, "" + return (not opened, url) # 点到点 → 打开 Modal + return (opened, "") @callback( - [ - Output(ElementIds.MODAL_HEADER, "children"), - ], - [ - Input(ElementIds.TAB_ONE_MAP, "clickData"), - ], + [Output(ElementIds.MODAL_HEADER, "children")], + [Input(ElementIds.TAB_ONE_MAP, "clickData")], prevent_initial_call=True, ) def change_text_modal(click_map): - """change the text of the modal header""" if click_map: return [f"Analyse data from {click_map['points'][0]['hovertext']}?"] return ["Analyse data from this location?"] @@ -377,10 +395,9 @@ def plot_location_epw_files(pathname): fig.update_layout(mapbox_style="carto-positron") fig.update_layout(margin={"r": 0, "t": 0, "l": 0, "b": 0}) - return ( - dcc.Graph( - id=ElementIds.TAB_ONE_MAP, - figure=fig, - config=generate_chart_name(TabNames.EPW_LOCATION_SELECT), - ), + return dcc.Graph( + id=ElementIds.TAB_ONE_MAP, + figure=fig, + config=generate_chart_name(TabNames.EPW_LOCATION_SELECT), + style={"position": "relative", "zIndex": 5}, ) diff --git a/pages/summary.py b/pages/summary.py index 832ab7e2..8dc257ea 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -1,9 +1,7 @@ import dash -import dash_bootstrap_components as dbc from dash.exceptions import PreventUpdate -from dash_extensions.enrich import dcc, html, Output, Input, State, callback +from dash_extensions.enrich import dcc, Output, Input, State, callback import dash_mantine_components as dmc - import plotly.graph_objects as go import requests @@ -36,11 +34,11 @@ def layout(): """Contents in the second tab 'Climate Summary'.""" - return dmc.Box( - className="container-col", - id=ElementIds.TAB_TWO_CONTAINER, + return dmc.Container( + fluid=True, + px="md", children=[ - # + dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER, gap="md"), ], ) @@ -57,134 +55,107 @@ def update_layout(si_ip): heating_setpoint = 50 cooling_setpoint = 64 - return dmc.Box( - className="container-col", + return dmc.Stack( id=ElementIds.TAB2_SCE1_CONTAINER, + gap="xl", children=[ dcc.Loading( type="circle", - children=dmc.Box( - className="container-col", + children=dmc.Stack( id=ElementIds.LOCATION_INFO, - style={"padding": "12px"}, + gap="xs", + p="md", ), ), dcc.Loading( type="circle", - children=dmc.Box(className="tab-two-section", id=ElementIds.WORLD_MAP), + children=dmc.Stack(id=ElementIds.WORLD_MAP, gap=0), ), - dmc.Box( - children=title_with_tooltip( - text="Download", - id_button=IdButtons.DOWNLOAD_BUTTON_LABEL, - tooltip_text="Use the following buttons to download either the Clima sourcefile or the EPW file", - ), + title_with_tooltip( + text="Download", + id_button=IdButtons.DOWNLOAD_BUTTON_LABEL, + tooltip_text="Use the following buttons to download either the Clima sourcefile or the EPW file", ), dcc.Loading( type="circle", - children=dbc.Row( - [ - dbc.Col( - dbc.Button( - "Download EPW", - color="primary", - id=ElementIds.DOWN_EPW_BUTTON, - ), - width="auto", - ), - dbc.Col( - dbc.Button( - "Download Clima dataframe", - color="primary", - id=ElementIds.DOWNLOAD_BUTTON, - ), - width="auto", + children=dmc.Group( + align="center", + justify="flex-start", + gap="md", + children=[ + dmc.Button( + "Download EPW", + id=ElementIds.DOWN_EPW_BUTTON, + color="blue", + variant="filled", ), - dbc.Col( - [ - dcc.Download(id=ElementIds.DOWNLOAD_DATAFRAME_CSV), - dcc.Download(id=ElementIds.DOWNLOAD_EPW), - ], - width=1, + dmc.Button( + "Download Clima dataframe", + id=ElementIds.DOWNLOAD_BUTTON, + color="blue", + variant="filled", ), + dcc.Download(id=ElementIds.DOWNLOAD_DATAFRAME_CSV), + dcc.Download(id=ElementIds.DOWNLOAD_EPW), ], ), ), - dmc.Box( - children=title_with_link( - text="Heating and Cooling Degree Days", - id_button=IdButtons.HDD_CDD_CHART, - doc_link=DocLinks.DEGREE_DAYS, - ), - ), - dbc.Alert( - "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!", - color="warning", - is_open=False, - id=ElementIds.WARNING_CDD_HIGHER_HDD, + title_with_link( + text="Heating and Cooling Degree Days", + id_button=IdButtons.HDD_CDD_CHART, + doc_link=DocLinks.DEGREE_DAYS, ), - dbc.Row( - [ - dbc.Col( - html.Label( - "Heating degree day (HDD) setpoint", - ), - width="auto", - ), - dbc.Col( - dbc.Input( - id=ElementIds.INPUT_HDD_SET_POINT, - type="number", - value=heating_setpoint, - style={"width": "4rem"}, - ), - width="auto", - ), - dbc.Col( - html.Label( - "Cooling degree day (CDD) setpoint", - ), - width="auto", + dmc.Stack(id=ElementIds.WARNING_CDD_HIGHER_HDD, gap=0), + dmc.Group( + align="center", + justify="center", + gap="md", + children=[ + dmc.Text("Heating degree day (HDD) setpoint"), + dmc.NumberInput( + id=ElementIds.INPUT_HDD_SET_POINT, + value=heating_setpoint, + step=1, + min=-100, + max=100, + w=80, + hideControls=False, ), - dbc.Col( - dbc.Input( - id=ElementIds.INPUT_CDD_SET_POINT, - type="number", - value=cooling_setpoint, - style={"width": "4rem"}, - ), - width="auto", + dmc.Text("Cooling degree day (CDD) setpoint"), + dmc.NumberInput( + id=ElementIds.INPUT_CDD_SET_POINT, + value=cooling_setpoint, + step=1, + min=-100, + max=100, + w=80, + hideControls=False, ), - dbc.Col( - dbc.Button( - id=ElementIds.SUBMIT_SET_POINTS, - children="Submit", - color="primary", - ), - width="auto", + dmc.Button( + id=ElementIds.SUBMIT_SET_POINTS, + children="Submit", + color="blue", + variant="filled", ), ], - align="center", - justify="center", ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER), + children=dmc.Stack(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER, gap=0), ), - dmc.Box( - children=title_with_link( - text="Climate Profiles", - id_button=IdButtons.CLIMATE_PROFILES_CHART, - doc_link=DocLinks.CLIMATE_PROFILES, - ), + title_with_link( + text="Climate Profiles", + id_button=IdButtons.CLIMATE_PROFILES_CHART, + doc_link=DocLinks.CLIMATE_PROFILES, ), - dbc.Row( + dmc.Grid( id=ElementIds.GRAPH_CONTAINER, + gutter="md", children=[ - dbc.Col(id=ElementIds.TEMP_PROFILE_GRAPH, width=12, md=6, lg=3), - dbc.Col(id=ElementIds.HUMIDITY_PROFILE_GRAPH, width=12, md=6, lg=3), - dbc.Col(id=ElementIds.SOLAR_RADIATION_GRAPH, width=12, md=6, lg=3), - dbc.Col(id=ElementIds.WIND_SPEED_GRAPH, width=12, md=6, lg=3), + dmc.GridCol(id=ElementIds.TEMP_PROFILE_GRAPH, span=3), + dmc.GridCol(id=ElementIds.HUMIDITY_PROFILE_GRAPH, span=3), + dmc.GridCol(id=ElementIds.SOLAR_RADIATION_GRAPH, span=3), + dmc.GridCol(id=ElementIds.WIND_SPEED_GRAPH, span=3), ], ), ], @@ -208,14 +179,11 @@ def update_layout(si_ip): ) def update_map(meta): """Update the contents of tab two. Passing in the general info (df, meta).""" - map_world = dcc.Graph( - id=ElementIds.GH_RAD_PROFILE_GRAPH, + return dcc.Graph( config=generate_chart_name(TabNames.MAP, meta), figure=world_map(meta), ) - return map_world - @callback( Output(ElementIds.LOCATION_INFO, "children"), @@ -232,94 +200,76 @@ def update_location_info(ts, df, meta, si_ip): lon = f"Longitude: {meta[ColNames.LON]}" lat = f"Latitude: {meta[ColNames.LAT]}" - site_elevation = float(meta[ColNames.SITE_ELEVATION]) - site_elevation = round(site_elevation, 2) - - elevation = f"Elevation above sea level: {str(site_elevation)} m" + site_elevation = round(float(meta[ColNames.SITE_ELEVATION]), 2) if si_ip != UnitSystem.SI: - site_elevation = site_elevation * 3.281 - site_elevation = round(site_elevation, 2) - elevation = f"Elevation above sea level: {str(site_elevation)} ft" + site_elevation = round(site_elevation * 3.281, 2) + elevation = f"Elevation above sea level: {site_elevation} ft" + + else: + elevation = f"Elevation above sea level: {site_elevation} m" period = "" if meta[ColNames.PERIOD]: start, stop = meta[ColNames.PERIOD].split("-") period = f"This file is based on data collected between {start} and {stop}" - r = requests.get( - f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[ColNames.LAT]}/{meta[ColNames.LON]}" - ) - climate_text = "" - if r.status_code == 200: - try: - climate_zone = r.json()["return_values"][0]["koppen_geiger_zone"] - zone_description = r.json()["return_values"][0]["zone_description"] - - climate_text = ( - f"Köppen–Geiger climate zone: {climate_zone}. {zone_description}." - ) - except KeyError: - pass + try: + r = requests.get( + f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[ColNames.LAT]}/{meta[ColNames.LON]}" + ) + if r.status_code == 200: + j = r.json()["return_values"][0] + climate_text = f"Köppen-Geiger climate zone: {j['koppen_geiger_zone']}. {j['zone_description']}." + except Exception: + pass # global horizontal irradiance # Note that the value is divided by 1000, so a corresponding change is made in the unit: total_solar_rad_value = round(df[ColNames.GLOB_HOR_RAD].sum() / 1000, 2) - total_solar_rad_unit = ( - "k" + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip][ColNames.UNIT] - ) + total_solar_rad_unit = "k" + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip][ + ColNames.UNIT + ].replace("", "").replace("", "") total_solar_rad = f"Annual cumulative horizontal solar radiation: {total_solar_rad_value} {total_solar_rad_unit}" glob_sum = df[ColNames.GLOB_HOR_RAD].sum() - if glob_sum > 0: - diffuse_percentage = round(df[ColNames.DIF_HOR_RAD].sum() / glob_sum * 100, 1) - else: - diffuse_percentage = 0 + diffuse_percentage = ( + round(df[ColNames.DIF_HOR_RAD].sum() / glob_sum * 100, 1) if glob_sum > 0 else 0 + ) total_diffuse_rad = ( f"Percentage of diffuse horizontal solar radiation: {diffuse_percentage} %" ) + tmp_unit = mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT] + average_yearly_tmp = ( - f"Average yearly temperature: {df[ColNames.DBT].mean().round(1)} " + tmp_unit - ) - hottest_yearly_tmp = ( - f"Hottest yearly temperature (99%): {df[ColNames.DBT].quantile(0.99).round(1)} " - + tmp_unit - ) - coldest_yearly_tmp = ( - f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} " - + tmp_unit + f"Average yearly temperature: {df[ColNames.DBT].mean().round(1)} {tmp_unit}" ) + hottest_yearly_tmp = f"Hottest yearly temperature (99%): {df[ColNames.DBT].quantile(0.99).round(1)} {tmp_unit}" + coldest_yearly_tmp = f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} {tmp_unit}" - location_info = dbc.Col( - [ - dbc.Row(location, style={"fontWeight": "bold"}), - dbc.Row(lon), - dbc.Row(lat), - dbc.Row(elevation), - dbc.Row(period), - dbc.Row(climate_text), - dbc.Row(average_yearly_tmp), - dbc.Row(hottest_yearly_tmp), - dbc.Row(coldest_yearly_tmp), - dbc.Row( - dcc.Markdown( - dangerously_allow_html=True, - children=[total_solar_rad], - style={"padding": 0}, - ) - ), - dbc.Row(total_diffuse_rad), + return dmc.Stack( + gap=4, + children=[ + dmc.Text(location, fw=700), + dmc.Text(lon), + dmc.Text(lat), + dmc.Text(elevation), + dmc.Text(period) if period else None, + dmc.Text(climate_text) if climate_text else None, + dmc.Text(average_yearly_tmp), + dmc.Text(hottest_yearly_tmp), + dmc.Text(coldest_yearly_tmp), + dmc.Text(total_solar_rad), + dmc.Text(total_diffuse_rad), ], ) - return location_info - @callback( [ Output(ElementIds.DEGREE_DAYS_CHART_WRAPPER, "children"), - Output(ElementIds.WARNING_CDD_HIGHER_HDD, "is_open"), + Output(ElementIds.WARNING_CDD_HIGHER_HDD, "children"), ], [ Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), @@ -346,9 +296,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ hdd_setpoint = hdd_value cdd_setpoint = cdd_value - warning_setpoint = False - if cdd_setpoint < hdd_setpoint: - warning_setpoint = True + warning_setpoint = cdd_setpoint < hdd_setpoint color_hdd = "red" color_cdd = "dodgerblue" @@ -363,18 +311,14 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ # calculates HDD per month query = query_month + str(i) + " and DBT<=" + str(hdd_setpoint) a = df.query(query)[ColNames.DBT].sub(hdd_setpoint) - hdd = a.sum(axis=0, skipna=True) - hdd = hdd / 24 - hdd = int(hdd) - hdd_array.append(hdd) + hdd = a.sum(axis=0, skipna=True) / 24 + hdd_array.append(int(hdd)) # calculates CDD per month query = query_month + str(i) + " and DBT>=" + str(cdd_setpoint) a = df.query(query)[ColNames.DBT].sub(cdd_setpoint) - cdd = a.sum(axis=0, skipna=True) - cdd = cdd / 24 - cdd = int(cdd) - cdd_array.append(cdd) + cdd = a.sum(axis=0, skipna=True) / 24 + cdd_array.append(int(cdd)) trace1 = go.Bar( x=months, @@ -397,11 +341,7 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ ), ) - data = [trace2, trace1] - - fig = go.Figure( - data=data, - ) + fig = go.Figure(data=[trace2, trace1]) fig.update_layout( barmode="relative", margin=tight_margins, @@ -424,7 +364,19 @@ def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ figure=fig, ) - return chart, warning_setpoint + alert_children = ( + dmc.Alert( + "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!", + color="yellow", + variant="filled", + title="Warning", + radius="md", + withCloseButton=True, + ) + if warning_setpoint + else None + ) + return chart, alert_children @callback( @@ -443,7 +395,6 @@ def update_violin_tdb(ts, global_local, df, meta, si_ip): units = generate_units_degree(si_ip) return dcc.Graph( id=ElementIds.TDB_PROFILE_GRAPH, - className="violin-container", config=generate_chart_name(TabNames.DRY_BULB_TEMPERATURE, meta, units), figure=violin(df, ColNames.DBT, global_local, si_ip), ) @@ -466,7 +417,6 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): units = generate_units(si_ip) return dcc.Graph( id=ElementIds.WIND_PROFILE_GRAPH, - className="violin-container", config=generate_chart_name(TabNames.WIND_SPEED, meta, units), figure=violin(df, ColNames.WIND_SPEED, global_local, si_ip), ) @@ -489,7 +439,6 @@ def update_tab_rh(ts, global_local, df, meta, si_ip): units = generate_units(si_ip) return dcc.Graph( id=ElementIds.RH_PROFILE_GRAPH, - className="violin-container", config=generate_chart_name(TabNames.RELATIVE_HUMIDITY, meta, units), figure=violin(df, ColNames.RH, global_local, si_ip), ) @@ -512,7 +461,6 @@ def update_tab_gh_rad(ts, global_local, df, meta, si_ip): units = generate_units(si_ip) return dcc.Graph( id=ElementIds.GH_RAD_PROFILE_GRAPH, - className="violin-container", config=generate_chart_name(TabNames.GLOBAL_HORIZONTAL_RADIATION, meta, units), figure=violin(df, ColNames.GLOB_HOR_RAD, global_local, si_ip), ) diff --git a/pages/sun.py b/pages/sun.py index 18794ff0..315be16d 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -3,9 +3,9 @@ import dash import dash_mantine_components as dmc -import dash_bootstrap_components as dbc + import numpy as np -from dash import html, dcc +from dash import dcc from dash_extensions.enrich import Output, Input, State, callback from pages.lib.global_column_names import ColNames @@ -56,25 +56,20 @@ def sun_path(): """Return the layout for the custom sun path and its dropdowns.""" - return dmc.Box( - className="container-col justify-center", + return dmc.Stack( + gap="md", children=[ - dmc.Box( - children=title_with_link( - text="Sun path chart", - id_button=IdButtons.SUN_PATH_CHART_LABEL, - doc_link=DocLinks.SUN_PATH_DIAGRAM, - ), + title_with_link( + text="Sun path chart", + id_button=IdButtons.SUN_PATH_CHART_LABEL, + doc_link=DocLinks.SUN_PATH_DIAGRAM, ), - dbc.Row( + dmc.Group( align="center", justify="center", + gap="md", children=[ - html.H6( - className="text-next-to-input", - children=["View: "], - style={"width": "10rem"}, - ), + dmc.Title("View: ", order=6, w="10rem", fz="xl"), dropdown( id=ElementIds.CUSTOM_SUN_VIEW_DROPDOWN, options={ @@ -86,15 +81,12 @@ def sun_path(): ), ], ), - dbc.Row( + dmc.Group( align="center", justify="center", + gap="md", children=[ - html.H6( - className="text-next-to-input", - children=["Select variable: "], - style={"width": "10rem"}, - ), + dmc.Title("Select Variable: ", order=6, w="10rem", fz="xl"), dropdown( id=ElementIds.CUSTOM_SUN_VAR_DROPDOWN, options=sc_dropdown_names, @@ -105,9 +97,7 @@ def sun_path(): ), dcc.Loading( type="circle", - children=dmc.Box( - id=ElementIds.CUSTOM_SUNPATH, - ), + children=dmc.Stack(id=ElementIds.CUSTOM_SUNPATH, w="100%"), ), ], ) @@ -115,24 +105,21 @@ def sun_path(): def explore_daily_heatmap(): """Contents of the bottom part of the tab""" - return dmc.Box( - className="container-col full-width", + return dmc.Stack( + gap="md", + w="100%", children=[ - dmc.Box( - children=title_with_link( - text="Daily charts", - id_button=IdButtons.DAILY_CHART_LABEL, - doc_link=DocLinks.CUSTOM_HEATMAP, - ), + title_with_link( + text="Daily charts", + id_button=IdButtons.DAILY_CHART_LABEL, + doc_link=DocLinks.CUSTOM_HEATMAP, ), - dmc.Box( - className="container-row justify-center align-center mb-2", + dmc.Group( + align="center", + justify="center", + gap="md", children=[ - html.H6( - className="text-next-to-input", - children=["Select variable: "], - style={"width": "10rem"}, - ), + dmc.Title("Select variable: ", order=6, w="10rem"), dropdown( id=ElementIds.TAB_EXPLORE_DROPDOWN, options=sun_cloud_tab_explore_dropdown_names, @@ -141,19 +128,22 @@ def explore_daily_heatmap(): ), ], ), - dcc.Loading(type="circle", children=dmc.Box(id=ElementIds.TAB4_DAILY)), + dcc.Loading( + type="circle", children=dmc.Stack(id=ElementIds.TAB4_DAILY, w="100%") + ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.TAB4_HEATMAP), + children=dmc.Stack(id=ElementIds.TAB4_HEATMAP, w="100%"), ), ], ) def static_section(): - return dmc.Box( + return dmc.Stack( id=ElementIds.STATIC_SECTION, - className="container-col full-width", + gap="md", + w="100%", children=[ # ... ], @@ -162,8 +152,9 @@ def static_section(): def layout(): """Contents of tab four.""" - return dmc.Box( - className="container-col", + return dmc.Stack( + gap="md", + w="100%", id=ElementIds.TAB_FOUR_CONTAINER, children=[sun_path(), static_section(), explore_daily_heatmap()], ) @@ -178,27 +169,23 @@ def update_static_section(si_ip): if si_ip == UnitSystem.IP: hor_unit = "Btu/ft²" return [ - dmc.Box( - children=title_with_link( - text="Global and Diffuse Horizontal Solar Radiation (" + hor_unit + ")", - id_button=IdButtons.MONTHLY_CHART_LABEL, - doc_link=DocLinks.SOLAR_RADIATION, - ), + title_with_link( + text="Global and Diffuse Horizontal Solar Radiation (" + hor_unit + ")", + id_button=IdButtons.MONTHLY_CHART_LABEL, + doc_link=DocLinks.SOLAR_RADIATION, ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.MONTHLY_SOLAR), + children=dmc.Stack(id=ElementIds.MONTHLY_SOLAR, w="100%"), ), - dmc.Box( - children=title_with_link( - text="Cloud coverage", - id_button=IdButtons.CLOUD_CHART_LABEL, - doc_link=DocLinks.CLOUD_COVER, - ), + title_with_link( + text="Cloud coverage", + id_button=IdButtons.CLOUD_CHART_LABEL, + doc_link=DocLinks.CLOUD_COVER, ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.CLOUD_COVER), + children=dmc.Stack(id=ElementIds.CLOUD_COVER, w="100%"), ), ] @@ -238,11 +225,13 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): ) units = generate_units(si_ip) return dcc.Graph( + style={"width": "100%", "height": "520px"}, config=generate_chart_name( TabNames.GLOBAL_AND_DIFFUSE_HORIZONTAL_SOLAR_RADIATION, meta, units ), figure=monthly, ), dcc.Graph( + style={"width": "100%", "height": "520px"}, config=generate_chart_name(TabNames.CLOUD_COVER, meta, units), figure=cover, ) @@ -268,6 +257,7 @@ def sun_path_chart(_, view, var, global_local, df, meta, si_ip): units = "" if var == "None" else generate_units(si_ip) if view == "polar": return dcc.Graph( + style={"width": "100%", "height": "520px"}, config=generate_chart_name( TabNames.SPHERICAL_SUNPATH, meta, custom_inputs, units ), @@ -275,6 +265,7 @@ def sun_path_chart(_, view, var, global_local, df, meta, si_ip): ) else: return dcc.Graph( + style={"width": "100%", "height": "520px"}, config=generate_chart_name( TabNames.CARTESIAN_SUNPATH, meta, custom_inputs, units ), @@ -300,6 +291,7 @@ def daily(_, var, global_local, df, meta, si_ip): custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return dcc.Graph( + style={"width": "100%", "height": "520px"}, config=generate_chart_name(TabNames.DAILY, meta, custom_inputs, units), figure=daily_profile(df, var, global_local, si_ip), ) @@ -322,6 +314,7 @@ def update_heatmap(_, var, global_local, df, meta, si_ip): custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return dcc.Graph( + style={"width": "100%", "height": "520px"}, config=generate_chart_name(TabNames.HEATMAP, meta, custom_inputs, units), figure=heatmap(df, var, global_local, si_ip), ) diff --git a/pages/t_rh.py b/pages/t_rh.py index 4d249f81..7fd9707d 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -1,6 +1,7 @@ import dash -from dash_extensions.enrich import Output, Input, State, dcc, html, callback +from dash_extensions.enrich import Output, Input, State, dcc, callback import dash_mantine_components as dmc + from config import PageUrls, DocLinks, PageInfo from pages.lib.global_scheme import dropdown_names from pages.lib.template_graphs import heatmap, yearly_profile, daily_profile @@ -31,69 +32,66 @@ def layout(): - return dmc.Box( - className="container-col full-width", + return dmc.Container( + fluid=True, + px="md", children=[ - dmc.Box( - className="container-row full-width align-center justify-center", + dmc.Group( + justify="center", + align="center", + gap="sm", + wrap="nowrap", children=[ - html.H4( - className="text-next-to-input", children=["Select a variable: "] - ), + dmc.Text("Select a variable:", fz="xl"), dropdown( id=ElementIds.ID_T_RH_DROPDOWN, - className="dropdown-t-rh", options={var: dropdown_names[var] for var in var_to_plot}, value=dropdown_names[var_to_plot[0]], + style={"width": "14rem"}, ), ], ), - dmc.Box( - className="container-col", + dmc.Stack( + gap="lg", + mt="md", children=[ - dmc.Box( - children=title_with_link( - text="Yearly Chart", - id_button=IdButtons.YEARLY_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), + # Yearly Chart + title_with_link( + text="Yearly Chart", + id_button=IdButtons.YEARLY_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.YEARLY_CHART), + children=dmc.Stack(id=ElementIds.YEARLY_CHART, gap=0), ), - dmc.Box( - children=title_with_link( - text="Daily chart", - id_button=IdButtons.DAILY_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), + # Daily chart + title_with_link( + text="Daily chart", + id_button=IdButtons.DAILY_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.DAILY), + children=dmc.Stack(id=ElementIds.DAILY, gap=0), ), - dmc.Box( - children=title_with_link( - text="Heatmap chart", - id_button=IdButtons.HEATMAP_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), + # Heatmap chart + title_with_link( + text="Heatmap chart", + id_button=IdButtons.HEATMAP_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.HEATMAP), - ), - dmc.Box( - children=title_with_tooltip( - text="Descriptive statistics", - tooltip_text="count, mean, std, min, max, and percentiles", - id_button=IdButtons.TABLE_TMP_RH, - ), + children=dmc.Stack(id=ElementIds.HEATMAP, gap=0), ), - dmc.Box( - id=ElementIds.TABLE_TMP_HUM, + # Descriptive statistics + title_with_tooltip( + text="Descriptive statistics", + tooltip_text="count, mean, std, min, max, and percentiles", + id_button=IdButtons.TABLE_TMP_RH, ), + dmc.Stack(id=ElementIds.TABLE_TMP_HUM, gap=0), ], ), ], @@ -193,7 +191,7 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): @callback( - [Output(ElementIds.HEATMAP, "children")], + Output(ElementIds.HEATMAP, "children"), [ Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), @@ -206,7 +204,7 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): ], ) def update_heatmap(_, global_local, dd_value, df, meta, si_ip): - """Update the contents of tab three. Passing in general info (df, meta).""" + """Update heatmap content.""" if dd_value == dropdown_names[var_to_plot[0]]: units = generate_units_degree(si_ip) return dcc.Graph( @@ -261,7 +259,7 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): ], ) def update_table(_, dd_value, df, si_ip): - """Update the contents of tab three. Passing in general info (df, meta).""" + """Update the contents of descriptive statistics table.""" return summary_table_tmp_rh_tab( df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], dd_value, diff --git a/pages/wind.py b/pages/wind.py index da2813bc..38204b9e 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -1,11 +1,11 @@ import dash -from dash import dcc, html +from dash import dcc import dash_mantine_components as dmc from dash_extensions.enrich import Output, Input, State, callback from pages.lib.global_element_ids import ElementIds from config import PageUrls, DocLinks, PageInfo -from pages.lib.global_scheme import month_lst, container_row_center_full +from pages.lib.global_scheme import month_lst from pages.lib.template_graphs import heatmap, wind_rose from pages.lib.global_column_names import ColNames from pages.lib.global_id_buttons import IdButtons @@ -30,14 +30,16 @@ def sliders(): """Returns 2 sliders for the hour""" - return dmc.Box( - className="container-col justify-center", + return dmc.Stack( id=ElementIds.SLIDER_CONTAINER, + gap="md", + align="center", children=[ - dmc.Box( - className="container-row each-slider", + dmc.Group( + gap="sm", + align="center", children=[ - html.P("Month Range"), + dmc.Text("Month Range"), dcc.RangeSlider( id=ElementIds.MONTH_SLIDER, min=1, @@ -50,10 +52,11 @@ def sliders(): ), ], ), - dmc.Box( - className="container-row each-slider", + dmc.Group( + gap="sm", + align="center", children=[ - html.P("Hour Range"), + dmc.Text("Hour Range"), dcc.RangeSlider( id=ElementIds.HOUR_SLIDER, min=1, @@ -72,87 +75,76 @@ def sliders(): def seasonal_wind_rose(): """Return the section with the 4 seasonal wind rose graphs.""" - return dmc.Box( - className="container-col", + return dmc.Stack( + gap="md", children=[ - dmc.Box( - children=title_with_link( - text="Seasonal Wind Rose", - id_button=IdButtons.SEASONAL_WIND_ROSE_DOC, - doc_link=DocLinks.WIND_ROSE, - ), + title_with_link( + text="Seasonal Wind Rose", + id_button=IdButtons.SEASONAL_WIND_ROSE_DOC, + doc_link=DocLinks.WIND_ROSE, ), - dmc.Box( - className=container_row_center_full, + dmc.Grid( + gutter="md", children=[ - dmc.Box( - className="container-col", - children=[ - dcc.Loading( - type="circle", - children=dmc.Box( - id=ElementIds.WINTER_WIND_ROSE, - className="daily-wind-graph", + dmc.GridCol( + span=6, + children=dmc.Stack( + gap="xs", + children=[ + dcc.Loading( + type="circle", + children=dmc.Stack( + id=ElementIds.WINTER_WIND_ROSE, w="100%" + ), ), - ), - html.P( - className="seasonal-text", - id=ElementIds.WINTER_WIND_ROSE_TEXT, - ), - ], + dmc.Text(id=ElementIds.WINTER_WIND_ROSE_TEXT), + ], + ), ), - dmc.Box( - className="container-col", - children=[ - dcc.Loading( - type="circle", - children=dmc.Box( - id=ElementIds.SPRING_WIND_ROSE, - className="daily-wind-graph", + dmc.GridCol( + span=6, + children=dmc.Stack( + gap="xs", + children=[ + dcc.Loading( + type="circle", + children=dmc.Stack( + id=ElementIds.SPRING_WIND_ROSE, w="100%" + ), ), - ), - html.P( - className="seasonal-text", - id=ElementIds.SPRING_WIND_ROSE_TEXT, - ), - ], + dmc.Text(id=ElementIds.SPRING_WIND_ROSE_TEXT), + ], + ), ), - ], - ), - dmc.Box( - className=container_row_center_full, - children=[ - dmc.Box( - className="container-col", - children=[ - dcc.Loading( - type="circle", - children=dmc.Box( - id=ElementIds.SUMMER_WIND_ROSE, - className="daily-wind-graph", + dmc.GridCol( + span=6, + children=dmc.Stack( + gap="xs", + children=[ + dcc.Loading( + type="circle", + children=dmc.Stack( + id=ElementIds.SUMMER_WIND_ROSE, w="100%" + ), ), - ), - html.P( - className="seasonal-text", - id=ElementIds.SUMMER_WIND_ROSE_TEXT, - ), - ], + dmc.Text(id=ElementIds.SUMMER_WIND_ROSE_TEXT), + ], + ), ), - dmc.Box( - className="container-col", - children=[ - dcc.Loading( - type="circle", - children=dmc.Box( - id=ElementIds.FALL_WIND_ROSE, - className="daily-wind-graph", + dmc.GridCol( + span=6, + children=dmc.Stack( + gap="xs", + children=[ + dcc.Loading( + type="circle", + children=dmc.Stack( + id=ElementIds.FALL_WIND_ROSE, w="100%" + ), ), - ), - html.P( - className="seasonal-text", - id=ElementIds.FALL_WIND_ROSE_TEXT, - ), - ], + dmc.Text(id=ElementIds.FALL_WIND_ROSE_TEXT), + ], + ), ), ], ), @@ -162,74 +154,62 @@ def seasonal_wind_rose(): def daily_wind_rose(): """Return the section for the 3 daily wind rose graphs.""" - return dmc.Box( - className="container-col full-width", + return dmc.Stack( + gap="md", id=ElementIds.TAB5_DAILY_CONTAINER, children=[ - dmc.Box( - children=title_with_link( - text="Daily Wind Rose", - id_button=IdButtons.DAILY_ROSE_CHART, - doc_link=DocLinks.WIND_ROSE, - ), + title_with_link( + text="Daily Wind Rose", + id_button=IdButtons.DAILY_ROSE_CHART, + doc_link=DocLinks.WIND_ROSE, ), - dmc.Box( - id=ElementIds.DAILY_WIND_ROSE_OUTER_CONTAINER, - className="container-row full-width", + dmc.Grid( + gutter="md", children=[ - dmc.Box( - className="container-col", - children=[ - dmc.Box( + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="xs", + children=[ dcc.Loading( type="circle", - children=dmc.Box( - className="daily-wind-graph", - id=ElementIds.MORNING_WIND_ROSE, + children=dmc.Stack( + id=ElementIds.MORNING_WIND_ROSE, w="100%" ), ), - ), - html.P( - className="daily-text", - id=ElementIds.MORNING_WIND_ROSE_TEXT, - ), - ], + dmc.Text(id=ElementIds.MORNING_WIND_ROSE_TEXT), + ], + ), ), - dmc.Box( - className="container-col", - children=[ - dmc.Box( + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="xs", + children=[ dcc.Loading( type="circle", - children=dmc.Box( - className="daily-wind-graph", - id=ElementIds.NOON_WIND_ROSE, + children=dmc.Stack( + id=ElementIds.NOON_WIND_ROSE, w="100%" ), ), - ), - html.P( - className="daily-text", - id=ElementIds.NOON_WIND_ROSE_TEXT, - ), - ], + dmc.Text(id=ElementIds.NOON_WIND_ROSE_TEXT), + ], + ), ), - dmc.Box( - className="container-col", - children=[ - dmc.Box( + dmc.GridCol( + span=4, + children=dmc.Stack( + gap="xs", + children=[ dcc.Loading( type="circle", - children=dmc.Box( - className="daily-wind-graph", - id=ElementIds.NIGHT_WIND_ROSE, + children=dmc.Stack( + id=ElementIds.NIGHT_WIND_ROSE, w="100%" ), ), - ), - html.P( - className="daily-text", - id=ElementIds.NIGHT_WIND_ROSE_TEXT, - ), - ], + dmc.Text(id=ElementIds.NIGHT_WIND_ROSE_TEXT), + ], + ), ), ], ), @@ -238,103 +218,119 @@ def daily_wind_rose(): def custom_wind_rose(): - return dmc.Box( - className="container-col justify-center full-width", + return dmc.Stack( + gap="md", + align="stretch", # stretch 让子项默认靠左 children=[ - dmc.Box( - children=title_with_tooltip( - text="Customizable Wind Rose", - tooltip_text=None, - id_button=IdButtons.CUSTOM_ROSE_CHART, - ), + # 标题靠左 + title_with_tooltip( + text="Customizable Wind Rose", + tooltip_text=None, + id_button=IdButtons.CUSTOM_ROSE_CHART, ), - dmc.Box( - className="container-row full-width justify-center", - id=ElementIds.TAB5_CUSTOM_DROPDOWN_CONTAINER, + # 参数区(保持居中排布) + dmc.Grid( + gutter="md", + justify="center", + align="center", + maw=900, + mx="auto", + w="100%", children=[ - dmc.Box( - className="container-col justify-center p-2 mr-2", - children=[ - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - style={"width": "8rem"}, - children=["Start Month:"], - ), - dropdown( - id=ElementIds.TAB5_CUSTOM_START_MONTH, - options={ - j: i + 1 for i, j in enumerate(month_lst) - }, - value=1, - style={"width": "6rem"}, - ), - ], - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - style={"width": "8rem"}, - children=["Start Hour:"], - ), - dropdown( - id=ElementIds.TAB5_CUSTOM_START_HOUR, - options={ - str(i) + ":00": i for i in range(0, 24) - }, - value=0, - style={"width": "6rem"}, - ), - ], - ), - ], + dmc.GridCol( + span=6, + children=dmc.Stack( + gap="md", + align="center", + children=[ + dmc.Group( + gap="md", + children=[ + dmc.Title( + "Start Month:", + order=6, + w="8rem", + ta="right", + ), + dropdown( + id=ElementIds.TAB5_CUSTOM_START_MONTH, + options={ + j: i + 1 + for i, j in enumerate(month_lst) + }, + value=1, + style={"width": "6rem"}, + ), + ], + ), + dmc.Group( + gap="md", + children=[ + dmc.Title( + "Start Hour:", order=6, w="8rem", ta="right" + ), + dropdown( + id=ElementIds.TAB5_CUSTOM_START_HOUR, + options={ + str(i) + ":00": i for i in range(0, 24) + }, + value=0, + style={"width": "6rem"}, + ), + ], + ), + ], + ), ), - dmc.Box( - className="container-col justify-center p-2 ml-2", - children=[ - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - style={"width": "8rem"}, - children=["End Month:"], - ), - dropdown( - id=ElementIds.TAB5_CUSTOM_END_MONTH, - options={ - j: i + 1 for i, j in enumerate(month_lst) - }, - value=12, - style={"width": "6rem"}, - ), - ], - ), - dmc.Box( - className=container_row_center_full, - children=[ - html.H6( - style={"width": "8rem"}, - children=["End Hour:"], - ), - dropdown( - id=ElementIds.TAB5_CUSTOM_END_HOUR, - options={ - str(i) + ":00": i for i in range(1, 25) - }, - value=24, - style={"width": "6rem"}, - ), - ], - ), - ], + dmc.GridCol( + span=6, + children=dmc.Stack( + gap="md", + align="center", + children=[ + dmc.Group( + gap="md", + children=[ + dmc.Title( + "End Month:", order=6, w="8rem", ta="right" + ), + dropdown( + id=ElementIds.TAB5_CUSTOM_END_MONTH, + options={ + j: i + 1 + for i, j in enumerate(month_lst) + }, + value=12, + style={"width": "6rem"}, + ), + ], + ), + dmc.Group( + gap="md", + children=[ + dmc.Title( + "End Hour:", order=6, w="8rem", ta="right" + ), + dropdown( + id=ElementIds.TAB5_CUSTOM_END_HOUR, + options={ + str(i) + ":00": i for i in range(1, 25) + }, + value=24, + style={"width": "6rem"}, + ), + ], + ), + ], + ), ), ], ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.CUSTOM_WIND_ROSE), + children=dmc.Stack( + id=ElementIds.CUSTOM_WIND_ROSE, w="100%", maw=900, mx="auto" + ), ), ], ) @@ -342,29 +338,26 @@ def custom_wind_rose(): def layout(): """Contents in the fifth tab 'Wind'.""" - return dmc.Box( - className="container-col justify-center", + return dmc.Stack( + gap="md", + align="stretch", children=[ - dmc.Box( - children=title_with_link( - text="Annual Wind Rose", - id_button=IdButtons.WIND_ROSE_LABEL, - doc_link=DocLinks.WIND_ROSE, - ), + title_with_link( + text="Annual Wind Rose", + id_button=IdButtons.WIND_ROSE_LABEL, + doc_link=DocLinks.WIND_ROSE, ), dcc.Loading( type="circle", - children=dmc.Box( - id=ElementIds.WIND_ROSE, - ), + children=dmc.Stack(id=ElementIds.WIND_ROSE, w="100%"), ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.WIND_SPEED), + children=dmc.Stack(id=ElementIds.WIND_SPEED, w="100%"), ), dcc.Loading( type="circle", - children=dmc.Box(id=ElementIds.WIND_DIRECTION), + children=dmc.Stack(id=ElementIds.WIND_DIRECTION, w="100%"), ), seasonal_wind_rose(), daily_wind_rose(), @@ -373,7 +366,6 @@ def layout(): ) -# wind rose @callback( Output(ElementIds.WIND_ROSE, "children"), Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), @@ -384,8 +376,6 @@ def layout(): ], ) def update_annual_wind_rose(_, df, meta, si_ip): - """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" - annual = wind_rose(df, "", [1, 12], [1, 24], True, si_ip) units = generate_units(si_ip) return dcc.Graph( @@ -394,10 +384,8 @@ def update_annual_wind_rose(_, df, meta, si_ip): ) -# wind speed @callback( Output(ElementIds.WIND_SPEED, "children"), - # General [ Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"), @@ -409,8 +397,6 @@ def update_annual_wind_rose(_, df, meta, si_ip): ], ) def update_tab_wind_speed(_, global_local, df, meta, si_ip): - """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" - speed = heatmap(df, ColNames.WIND_SPEED, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( @@ -419,13 +405,9 @@ def update_tab_wind_speed(_, global_local, df, meta, si_ip): ) -# wind direction @callback( Output(ElementIds.WIND_DIRECTION, "children"), - # General - [ - Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"), - ], + [Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value")], [ State(ElementIds.ID_WIND_DF_STORE, "data"), State(ElementIds.ID_WIND_META_STORE, "data"), @@ -433,8 +415,6 @@ def update_tab_wind_speed(_, global_local, df, meta, si_ip): ], ) def update_tab_wind_direction(global_local, df, meta, si_ip): - """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" - direction = heatmap(df, ColNames.WIND_DIR, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( @@ -443,10 +423,8 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): ) -# Custom Wind rose @callback( Output(ElementIds.CUSTOM_WIND_ROSE, "children"), - # Custom Graph Input [ Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), Input(ElementIds.TAB5_CUSTOM_START_MONTH, "value"), @@ -463,14 +441,11 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): def update_custom_wind_rose( _, start_month, start_hour, end_month, end_hour, df, meta, si_ip ): - """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" - start_hour = int(start_hour) end_hour = int(end_hour) start_month = int(start_month) end_month = int(end_month) - # Wind Rose Graphs if start_month <= end_month: df = df.loc[ (df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month) @@ -483,6 +458,7 @@ def update_custom_wind_rose( df = df.loc[(df[ColNames.HOUR] >= start_hour) & (df[ColNames.HOUR] <= end_hour)] else: df = df.loc[(df[ColNames.HOUR] <= end_hour) | (df[ColNames.HOUR] >= start_hour)] + custom = wind_rose( df, "", [start_month, end_month], [start_hour, end_hour], True, si_ip ) @@ -509,9 +485,7 @@ def update_custom_wind_rose( Output(ElementIds.SUMMER_WIND_ROSE_TEXT, "children"), Output(ElementIds.FALL_WIND_ROSE_TEXT, "children"), ], - [ - Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), - ], + [Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp")], [ State(ElementIds.ID_WIND_DF_STORE, "data"), State(ElementIds.ID_WIND_META_STORE, "data"), @@ -525,18 +499,17 @@ def update_seasonal_graphs(_, df, meta, si_ip): summer_months = [6, 8] fall_months = [9, 12] - # Wind Rose Graphs winter = wind_rose(df, "", winter_months, hours, False, si_ip) spring = wind_rose(df, "", spring_months, hours, True, si_ip) summer = wind_rose(df, "", summer_months, hours, False, si_ip) fall = wind_rose(df, "", fall_months, hours, False, si_ip) - # Text + query_calm_wind = f"{ColNames.WIND_SPEED} == 0" + winter_df = df.loc[ (df[ColNames.MONTH] <= winter_months[1]) | (df[ColNames.MONTH] >= winter_months[0]) ] - query_calm_wind = f"{ColNames.WIND_SPEED} == 0" winter_total_count = winter_df.shape[0] winter_calm_count = winter_df.query(query_calm_wind).shape[0] @@ -565,8 +538,7 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): f"Observations between the months of {month_start} and {month_end} " f"between 01:00 hours and 24:00 hours. " f"Selected observations {str(count)} of 8760, or " - f"{str(int(100 * (count / 8760)))} %. {str(n_calm)} observations have " - f"calm winds." + f"{str(int(100 * (count / 8760)))} %. {str(n_calm)} observations have calm winds." ) winter_text = seasonal_chart_caption( @@ -593,6 +565,7 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): fall_total_count, fall_calm_count, ) + units = generate_units(si_ip) return ( dcc.Graph( @@ -619,7 +592,6 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): @callback( - # Daily Graphs [ Output(ElementIds.MORNING_WIND_ROSE, "children"), Output(ElementIds.NOON_WIND_ROSE, "children"), @@ -628,7 +600,6 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): Output(ElementIds.NOON_WIND_ROSE_TEXT, "children"), Output(ElementIds.NIGHT_WIND_ROSE_TEXT, "children"), ], - # General Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), [ State(ElementIds.ID_WIND_DF_STORE, "data"), @@ -637,20 +608,17 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): ], ) def update_daily_graphs(_, df, meta, si_ip): - """Update the contents of tab five. Passing in the info from the sliders and the general info (df, meta).""" - months = [1, 12] morning_times = [6, 13] noon_times = [14, 21] night_times = [22, 5] - # Wind Rose Graphs morning = wind_rose(df, "", months, morning_times, False, si_ip) noon = wind_rose(df, "", months, noon_times, False, si_ip) night = wind_rose(df, "", months, night_times, True, si_ip) - # Text query_calm_wind = f"{ColNames.WIND_SPEED} == 0" + morning_df = df.loc[ (df[ColNames.HOUR] >= morning_times[0]) & (df[ColNames.HOUR] <= morning_times[1]) @@ -676,21 +644,19 @@ def daily_chart_caption(hour_start, hour_end, count, calm_count): f"Observations between the months of Jan and Dec between " f"{str(hour_start)}:00 hours and {str(hour_end)}:00 hours. " f"Selected observations {count} of 8760, or " - f"{str(int(100 * (count / 8760)))}%. {calm_count} " - f"observations have calm winds." + f"{str(int(100 * (count / 8760)))}%. {calm_count} observations have calm winds." ) morning_text = daily_chart_caption( morning_times[0], morning_times[1], morning_total_count, morning_calm_count ) - noon_text = daily_chart_caption( noon_times[0], noon_times[1], noon_total_count, noon_calm_count ) - night_text = daily_chart_caption( night_times[0], night_times[1], night_total_count, night_calm_count ) + units = generate_units(si_ip) return ( dcc.Graph( diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index b946fc72..26013789 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -45,7 +45,7 @@ describe('Clima', () => { cy.contains('Latitude: 44.5308'); cy.contains('Elevation above sea level: 37.0 m'); cy.contains('This file is based on data collected between 2004 and 2018'); - cy.contains('Köppen–Geiger climate zone: Cfa. Humid subtropical, no dry season.'); + cy.contains('Köppen-Geiger climate zone: Cfa. Humid subtropical, no dry season.'); cy.contains('Average yearly temperature: 14.5 °C'); cy.contains('Hottest yearly temperature (99%): 34.0 °C'); cy.contains('Coldest yearly temperature (1%): -2.0 °C'); From 2539f52a311b1b0324bc2e6a44e6a423194f64e8 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Fri, 12 Sep 2025 18:38:06 +1000 Subject: [PATCH 085/163] add: created a new class file to store icon value --- pages/lib/page_icon.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 pages/lib/page_icon.py diff --git a/pages/lib/page_icon.py b/pages/lib/page_icon.py new file mode 100644 index 00000000..6515a6b1 --- /dev/null +++ b/pages/lib/page_icon.py @@ -0,0 +1,32 @@ +class PageIcon: + """Page icon mappings - optimized with dictionary.""" + + # Page Name to icon Mapping + _ICON_MAP = { + "Select Weather File": "tabler:upload", + "Climate Summary": "tabler:chart-bar", + "Temperature and Humidity": "tabler:temperature", + "Sun and Clouds": "tabler:sun", + "Wind": "tabler:wind", + "Psychrometric Chart": "tabler:chart-dots", + "Natural Ventilation": "tabler:windmill", + "Outdoor Comfort": "tabler:thermometer", + "Data Explorer": "tabler:database", + "Changelog": "tabler:history", + } + + SELECT_WEATHER_FILE = _ICON_MAP["Select Weather File"] + CLIMATE_SUMMARY = _ICON_MAP["Climate Summary"] + TEMPERATURE_AND_HUMIDITY = _ICON_MAP["Temperature and Humidity"] + SUN_AND_CLOUDS = _ICON_MAP["Sun and Clouds"] + WIND = _ICON_MAP["Wind"] + PSYCHROMETRIC_CHART = _ICON_MAP["Psychrometric Chart"] + NATURAL_VENTILATION = _ICON_MAP["Natural Ventilation"] + OUTDOOR_COMFORT = _ICON_MAP["Outdoor Comfort"] + DATA_EXPLORER = _ICON_MAP["Data Explorer"] + CHANGELOG = _ICON_MAP["Changelog"] + + @classmethod + def get_icon(cls, page_name): + """Get icon for a page name.""" + return cls._ICON_MAP.get(page_name, "tabler:circle") From 6d7a4aa98079a604bdd49cf6360fd68213fd7883 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Sun, 14 Sep 2025 19:25:05 +1000 Subject: [PATCH 086/163] fix: removed all html and dash bootstrap via using dash mantine components --- pages/explorer.py | 31 ------------------------------- pages/lib/layout.py | 2 +- pages/natural_ventilation.py | 16 +++++++++------- pages/outdoor.py | 7 ++----- pages/psy-chart.py | 10 ---------- pages/select.py | 22 ++++++++++------------ 6 files changed, 22 insertions(+), 66 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index 68898ee3..442cc367 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -101,7 +101,6 @@ def section_one(): w="100%", ), ), - # Daily chart title_with_link( text="Daily chart", id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL, @@ -116,7 +115,6 @@ def section_one(): w="100%", ), ), - # Heatmap chart title_with_link( text="Heatmap chart", id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL, @@ -151,7 +149,6 @@ def section_one(): size="md", radius="md", ), - # Month Range 行(3-6-3) dmc.Grid( gutter="sm", align="center", @@ -243,17 +240,14 @@ def section_two_inputs(): w="100%", gap="md", children=[ - # 标题(保留你已有的封装) title_with_tooltip( text="Customizable heatmap", tooltip_text=None, id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL, ), - # 三列区域:①变量选择 ②时间过滤(月份/小时)③数据过滤(变量/最小/最大) dmc.Grid( gutter="md", children=[ - # ① 变量选择列 dmc.GridCol( span=4, children=dmc.Stack( @@ -280,7 +274,6 @@ def section_two_inputs(): ], ), ), - # ② 时间过滤列(按钮 + 月份范围 + 小时范围) dmc.GridCol( span=4, children=dmc.Stack( @@ -294,7 +287,6 @@ def section_two_inputs(): size="md", radius="md", ), - # Month Range:3/6/3 dmc.Grid( gutter="sm", align="center", @@ -334,7 +326,6 @@ def section_two_inputs(): ), ], ), - # Hour Range:3/6/3 dmc.Grid( gutter="sm", align="center", @@ -377,7 +368,6 @@ def section_two_inputs(): ], ), ), - # ③ 数据过滤列(按钮 + 过滤变量 + 最小/最大值) dmc.GridCol( span=4, children=dmc.Stack( @@ -410,7 +400,6 @@ def section_two_inputs(): ), ], ), - # Min Value dmc.Grid( gutter="sm", align="center", @@ -431,7 +420,6 @@ def section_two_inputs(): ), ], ), - # Max Value dmc.Grid( gutter="sm", align="center", @@ -469,9 +457,7 @@ def section_two(): gap="md", align="center", children=[ - # 输入表单 section_two_inputs(), - # 自定义热力图 dcc.Loading( type="circle", children=dmc.Paper( @@ -481,7 +467,6 @@ def section_two(): w="100%", ), ), - # Normalize 复选框 dmc.Group( gap="sm", children=[ @@ -494,7 +479,6 @@ def section_two(): ), ], ), - # Summary 图表 dcc.Loading( type="circle", children=dmc.Paper( @@ -520,13 +504,11 @@ def section_three_inputs(): dmc.Grid( gutter="md", children=[ - # ① X/Y/Color By 列 dmc.GridCol( span=4, children=dmc.Stack( gap="sm", children=[ - # X Variable dmc.Grid( gutter="sm", align="center", @@ -544,7 +526,6 @@ def section_three_inputs(): ), ], ), - # Y Variable dmc.Grid( gutter="sm", align="center", @@ -562,7 +543,6 @@ def section_three_inputs(): ), ], ), - # Color By dmc.Grid( gutter="sm", align="center", @@ -583,7 +563,6 @@ def section_three_inputs(): ], ), ), - # ② 时间过滤(月/小时) dmc.GridCol( span=4, children=dmc.Stack( @@ -597,7 +576,6 @@ def section_three_inputs(): size="md", radius="md", ), - # Month Range:3/6/3 dmc.Grid( gutter="sm", align="center", @@ -636,7 +614,6 @@ def section_three_inputs(): ), ], ), - # Hour Range:3/6/3 dmc.Grid( gutter="sm", align="center", @@ -678,7 +655,6 @@ def section_three_inputs(): ], ), ), - # ③ 数据过滤(变量/最小/最大) dmc.GridCol( span=4, children=dmc.Stack( @@ -692,7 +668,6 @@ def section_three_inputs(): size="md", radius="md", ), - # Filter Variable dmc.Grid( gutter="sm", align="center", @@ -711,7 +686,6 @@ def section_three_inputs(): ), ], ), - # Min Value dmc.Grid( gutter="sm", align="center", @@ -731,7 +705,6 @@ def section_three_inputs(): ), ], ), - # Max Value dmc.Grid( gutter="sm", align="center", @@ -766,15 +739,12 @@ def section_three(): w="100%", gap="md", children=[ - # 标题(保留你现有的封装) title_with_tooltip( text="More charts", tooltip_text=None, id_button=IdButtons.MORE_CHARTS_LABEL, ), - # 输入区 section_three_inputs(), - # 图 1:THREE_VAR dcc.Loading( type="circle", children=dmc.Paper( @@ -784,7 +754,6 @@ def section_three(): w="100%", ), ), - # 图 2:TWO_VAR dcc.Loading( type="circle", children=dmc.Paper( diff --git a/pages/lib/layout.py b/pages/lib/layout.py index a5c61791..e47b9eea 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -50,7 +50,7 @@ def alert(): ), dcc.Interval( id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, - interval=12 * 500, + interval=12 * 1000, n_intervals=0, ), ], diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index ea6835fd..0c2d210f 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,7 +1,6 @@ import dash from dash import dcc import dash_mantine_components as dmc -import dash_bootstrap_components as dbc from dash_extensions.enrich import Output, Input, State, callback import numpy as np @@ -355,12 +354,15 @@ def nv_heatmap( if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: return ( - dbc.Alert( - "Natural ventilation is not available in this location under these" - " conditions. Please either select a different outdoor dry-bulb air" - " temperature range, change the month and hour filter, or increase" - " thedew-point temperature.", - color="danger", + dmc.Alert( + title="Notice", + color="red", + children=( + "Natural ventilation is not available in this location under these " + "conditions. Please either select a different outdoor dry-bulb air " + "temperature range, change the month and hour filter, or increase " + "the dew-point temperature." + ), style={"text-align": "center", "marginTop": "2rem"}, ), ) diff --git a/pages/outdoor.py b/pages/outdoor.py index c33c8b5c..d3cececb 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -77,7 +77,6 @@ def inputs_outdoor_comfort(): radius="md", w="100%", ), - # Month Range dmc.Grid( gutter="sm", align="center", @@ -111,7 +110,6 @@ def inputs_outdoor_comfort(): ), ], ), - # Hour Range dmc.Grid( gutter="sm", align="center", @@ -157,7 +155,6 @@ def outdoor_comfort_chart(): w="100%", gap="md", children=[ - # 输出区域 dmc.Paper( id=ElementIds.OUTDOOR_COMFORT_OUTPUT, radius="md", @@ -196,7 +193,7 @@ def outdoor_comfort_chart(): h=400, ), ), - # Normalize data 开关 + Tooltip + # Normalize data dmc.Group( align="center", justify="center", @@ -442,7 +439,7 @@ def update_tab_utci_category( [ Input(ElementIds.TAB7_DROPDOWN, "value"), Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), - Input(ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, "value"), + Input(ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, "checked"), ], [ State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 672151dc..a2dfa61b 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -65,7 +65,6 @@ def inputs(): dmc.Grid( gutter="md", children=[ - # ① Color By dmc.GridCol( span=4, children=dmc.Stack( @@ -93,7 +92,6 @@ def inputs(): ], ), ), - # ② 时间过滤(月/小时) dmc.GridCol( span=4, children=dmc.Stack( @@ -107,7 +105,6 @@ def inputs(): size="md", radius="md", ), - # Month Range:3/6/3 dmc.Grid( gutter="sm", align="center", @@ -146,7 +143,6 @@ def inputs(): ), ], ), - # Hour Range:3/6/3 dmc.Grid( gutter="sm", align="center", @@ -188,7 +184,6 @@ def inputs(): ], ), ), - # ③ 数据过滤(变量/最小/最大) dmc.GridCol( span=4, children=dmc.Stack( @@ -202,7 +197,6 @@ def inputs(): size="md", radius="md", ), - # Filter Variable dmc.Grid( gutter="sm", align="center", @@ -221,7 +215,6 @@ def inputs(): ), ], ), - # Min Value dmc.Grid( gutter="sm", align="center", @@ -241,7 +234,6 @@ def inputs(): ), ], ), - # Max Value dmc.Grid( gutter="sm", align="center", @@ -275,13 +267,11 @@ def layout(): w="100%", gap="md", children=[ - # 标题(保留封装) title_with_link( text="Psychrometric Chart", id_button=IdButtons.PSYCHROMETRIC_CHART_CHART, doc_link=DocLinks.PSYCHROMETRIC_CHART, ), - # 内容区:输入区 + 图表 dcc.Loading( type="circle", children=dmc.Stack( diff --git a/pages/select.py b/pages/select.py index fefa5872..738a6818 100644 --- a/pages/select.py +++ b/pages/select.py @@ -3,7 +3,6 @@ import re import dash -import dash_bootstrap_components as dbc import dash_mantine_components as dmc import pandas as pd import plotly.express as px @@ -123,12 +122,11 @@ def layout(): def alert(): """Alert layout for the submit button.""" - return dbc.Alert( + return dmc.Alert( messages_alert["start"], - color="primary", + color="blue", id=ElementIds.ALERT, - dismissable=False, - is_open=True, + withCloseButton=False, style={"maxHeight": "66px"}, ) @@ -138,7 +136,7 @@ def alert(): [ Output(ElementIds.ID_SELECT_META_STORE, "data"), Output(ElementIds.ID_SELECT_LINES_STORE, "data"), - Output(ElementIds.ALERT, "is_open"), + Output(ElementIds.ALERT, "visible"), Output(ElementIds.ALERT, "children"), Output(ElementIds.ALERT, "color"), ], @@ -172,7 +170,7 @@ def submitted_data( None, True, messages_alert["not_available"], - "warning", + "orange", ) location_info = get_location_info( lines, url_store @@ -182,7 +180,7 @@ def submitted_data( lines, True, messages_alert["success"], - "success", + "green", ) elif ( @@ -206,7 +204,7 @@ def submitted_data( lines, True, messages_alert["success"], - "success", + "green", ) else: return ( @@ -214,7 +212,7 @@ def submitted_data( None, True, messages_alert["invalid_format"], - "warning", + "orange", ) except (ValueError, IndexError, KeyError) as e: print(f"Error parsing EPW file: {e}") @@ -223,7 +221,7 @@ def submitted_data( None, True, messages_alert["wrong_extension"], - "warning", + "orange", ) raise PreventUpdate @@ -328,7 +326,7 @@ def display_modal_when_data_clicked(_, click_map, __, opened): url = re.search( r'href=[\'"]?([^\'" >]+)', click_map["points"][0]["customdata"][-1] ).group(1) - return (not opened, url) # 点到点 → 打开 Modal + return (not opened, url) return (opened, "") From 316c8812a1cf1485bfcfbd5ddbed951eae51cdf5 Mon Sep 17 00:00:00 2001 From: yluo0664 <154036738+LeoLuosifen@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:32:06 +1000 Subject: [PATCH 087/163] fix: fixed the components that ensured cypress testing could be passed --- pages/lib/utils.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/pages/lib/utils.py b/pages/lib/utils.py index dc20810a..8e534d90 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -4,7 +4,7 @@ import math import pandas as pd -from dash import dash_table, dcc +from dash import html, dash_table, dcc import dash_mantine_components as dmc from config import UnitSystem @@ -159,19 +159,15 @@ def title_with_tooltip(text, tooltip_text, id_button): label=tooltip_text, position="right", withArrow=True, - multiline=True, - w=220, - children=dmc.ActionIcon( + children=[ dmc.Image( id=id_button, src="/assets/icons/help.png", alt="help", w=16, h=16, - ), - variant="transparent", - size="sm", - ), + ) + ], ), ], ) @@ -204,10 +200,8 @@ def title_with_link( label=tooltip_text, position="right", withArrow=True, - multiline=True, - w=220, - children=dmc.Anchor( - dmc.ActionIcon( + children=[ + html.A( dmc.Image( id=id_button, src="/assets/icons/book.png", @@ -215,12 +209,10 @@ def title_with_link( w=16, h=16, ), - variant="transparent", - size="sm", - ), - href=doc_link, - target="_blank", - ), + href=doc_link, + target="_blank", + ) + ], ), ], ) From 682cea43f24b2460670ca42647457f20271f6fc8 Mon Sep 17 00:00:00 2001 From: yluo0664 Date: Wed, 17 Sep 2025 18:36:57 +1000 Subject: [PATCH 088/163] refactor: used AppShell in Dash Mantine to manage the whole layout - optimized the original simple dmc components - removed the element ids are no longer needed - removed the unnecessary css style in css files - removed page_icon.py, moved to layout.py and renamed class name to NavbarIcons - updated spec.cy.js - reformatted the code --- assets/layout.css | 12 - assets/tabs.css | 16 - main.py | 19 +- pages/explorer.py | 865 ++++++++++++------------------ pages/lib/global_element_ids.py | 17 +- pages/lib/layout.py | 583 +++++++++----------- pages/lib/page_icon.py | 32 -- pages/lib/utils.py | 12 +- pages/natural_ventilation.py | 328 +++++------ pages/outdoor.py | 207 +++---- pages/psy-chart.py | 326 +++++------ pages/select.py | 4 - pages/summary.py | 191 +++---- pages/sun.py | 17 +- pages/t_rh.py | 10 +- pages/wind.py | 55 +- tests/node/cypress/e2e/spec.cy.js | 7 +- 17 files changed, 1100 insertions(+), 1601 deletions(-) delete mode 100644 pages/lib/page_icon.py diff --git a/assets/layout.css b/assets/layout.css index 611c3a11..42ed6cb2 100644 --- a/assets/layout.css +++ b/assets/layout.css @@ -29,15 +29,3 @@ width: 100%; //or any percentage width you want } -/* begin displaying banner below the banner */ -.custom-sidebar .mantine-Drawer-content { - top: 80px !important; - height: calc(100vh - 80px) !important; - position: fixed !important; -} - -.custom-sidebar .mantine-Drawer-overlay { - top: 80px !important; - height: calc(100vh - 80px) !important; -} - diff --git a/assets/tabs.css b/assets/tabs.css index 10a44d92..1813deda 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -287,19 +287,3 @@ p { z-index: 1000; } -/* SegmentedControl adapt to container width */ -#sidebar .mantine-SegmentedControl-root { - width: 100% !important; -} - -#sidebar .mantine-SegmentedControl-control { - flex: 1 !important; - min-width: 0 !important; -} - -/* Response design */ -@media (max-width: 768px) { - #sidebar { - width: 280px !important; - } -} \ No newline at end of file diff --git a/main.py b/main.py index e74a01af..cdf1b941 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,8 @@ from dash import dcc -from dash_extensions.enrich import Output, Input, callback import dash_mantine_components as dmc from app import app -from pages.lib.layout import banner, footer, build_tabs, sidebar +from pages.lib.layout import create_collapsible_layout from config import AppConfig from pages.lib.global_element_ids import ElementIds @@ -11,26 +10,12 @@ app.title = AppConfig.TITLE app.layout = dmc.MantineProvider( - theme={"colorScheme": "light", "primaryColor": "blue"}, children=[ dcc.Location(id=ElementIds.MAIN_URL, refresh=False), - sidebar(), - banner(), - dmc.Box(id=ElementIds.PAGE_CONTENT, children=build_tabs()), - footer(), + create_collapsible_layout(), ], ) - -# callback for survey alert (dbc.Toast) -@callback( - Output(ElementIds.ID_MAIN_ALERT_AUTO, "is_open"), - Input(ElementIds.ID_MAIN_INTERVAL_COMPONENT, "n_intervals"), -) -def display_alert(n): - return n == 1 - - if __name__ == "__main__": app.run( debug=AppConfig.DEBUG, diff --git a/pages/explorer.py b/pages/explorer.py index 442cc367..bc36fc65 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -63,13 +63,10 @@ def section_one_inputs(): """Return the inputs from section one.""" return dmc.Group( - align="center", + mt="md", justify="center", - gap="sm", - wrap=False, - w="100%", children=[ - dmc.Title("Select a variable:", order=4), + dmc.Title("Select a variable:", order=5), dropdown( id=ElementIds.SEC1_VAR_DROPDOWN, options=explore_dropdown_names, @@ -82,8 +79,6 @@ def section_one_inputs(): def section_one(): """Return the graphs for section one""" return dmc.Stack( - w="100%", - gap="md", children=[ section_one_inputs(), # Yearly chart @@ -97,8 +92,6 @@ def section_one(): children=dmc.Paper( id=ElementIds.YEARLY_EXPLORE, p="sm", - radius="md", - w="100%", ), ), title_with_link( @@ -111,8 +104,6 @@ def section_one(): children=dmc.Paper( id=ElementIds.QUERY_DAILY, p="sm", - radius="md", - w="100%", ), ), title_with_link( @@ -125,8 +116,6 @@ def section_one(): children=dmc.Paper( id=ElementIds.QUERY_HEATMAP, p="sm", - radius="md", - w="100%", ), ), title_with_tooltip( @@ -134,102 +123,83 @@ def section_one(): tooltip_text="count, mean, std, min, max, and percentiles", id_button=IdButtons.TABLE_EXPLORE, ), - dmc.Center( - w="100%", - children=dmc.Stack( - gap="sm", - w="100%", - maw=600, - children=[ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.SEC1_TIME_FILTER_INPUT, - variant="filled", - color="blue", - size="md", - radius="md", - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=3, - children=dmc.Text("Month Range"), - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.SEC1_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, + dmc.SimpleGrid( + cols={"base": 1, "md": 3}, + children=[ + dmc.Stack(), + dmc.Stack( + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.SEC1_TIME_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + fullWidth=True, + ), + # Month + dmc.Flex( + children=[ + dmc.Text("Month Range", miw=110), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.SEC1_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, + ), ), - ), - dmc.GridCol( - span=3, - children=dcc.Checklist( + dcc.Checklist( id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, options=[ {"label": "Invert", "value": "invert"} ], value=[], ), - ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=3, - children=dmc.Text("Hour Range"), - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.SEC1_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, + ], + ), + dmc.Flex( + children=[ + dmc.Text("Hour Range", miw=110), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.SEC1_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, + ), ), - ), - dmc.GridCol( - span=3, - children=dcc.Checklist( + dcc.Checklist( id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, options=[ {"label": "Invert", "value": "invert"} ], value=[], ), - ), - ], - ), - ], - ), - ), - dmc.Paper( - id=ElementIds.TABLE_DATA_EXPLORER, - p="sm", - radius="md", - w="100%", + ], + ), + ], + ), + dmc.Stack(), + ], ), + dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"), ], ) @@ -237,211 +207,152 @@ def section_one(): def section_two_inputs(): """Return all the input forms from section two.""" return dmc.Stack( - w="100%", - gap="md", children=[ title_with_tooltip( text="Customizable heatmap", tooltip_text=None, id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL, ), - dmc.Grid( - gutter="md", + dmc.SimpleGrid( + cols={"base": 1, "md": 3}, children=[ - dmc.GridCol( - span=4, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, - children=dmc.Text("Variable:"), - ), - dmc.GridCol( - span=8, - children=dropdown( - id=ElementIds.SEC2_VAR_DROPDOWN, - options=explore_dropdown_names, - value=ColNames.RH, - ), + dmc.Stack( + children=[ + dmc.Flex( + children=[ + dmc.Text("Variable:", miw=110, ml="md"), + dmc.Stack( + flex=1, + children=dropdown( + id=ElementIds.SEC2_VAR_DROPDOWN, + options=explore_dropdown_names, + value=ColNames.RH, ), - ], - ), - ], - ), + ), + ], + ), + ], ), - dmc.GridCol( - span=4, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.SEC2_TIME_FILTER_INPUT, - variant="filled", - color="blue", - size="md", - radius="md", - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=3, - children=dmc.Text("Month Range"), - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.SEC2_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, - options=[ - { - "label": "Invert", - "value": "invert", - } - ], - value=[], - ), - ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=3, - children=dmc.Text("Hour Range"), - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.SEC2_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, - ), + dmc.Stack( + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.SEC2_TIME_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + fullWidth=True, + ), + dmc.Flex( + children=[ + dmc.Text("Month Range", miw=110), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.SEC2_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, - options=[ - { - "label": "Invert", - "value": "invert", - } - ], - value=[], - ), + ), + dcc.Checklist( + id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, + options=[ + {"label": "Invert", "value": "invert"} + ], + value=[], + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Hour Range", miw=110), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.SEC2_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, ), - ], - ), - ], - ), + ), + dcc.Checklist( + id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, + options=[ + {"label": "Invert", "value": "invert"} + ], + value=[], + ), + ], + ), + ], ), - dmc.GridCol( - span=4, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Button( - "Apply filter", - id=ElementIds.SEC2_DATA_FILTER_INPUT, - variant="filled", - color="blue", - size="md", - radius="md", - ), - # Filter Variable - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, - children=dmc.Text("Filter Variable:"), - ), - dmc.GridCol( - span=8, - children=dropdown( - id=ElementIds.SEC2_DATA_FILTER_VAR, - options=explore_dropdown_names, - value=ColNames.RH, - ), - ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, - children=dmc.Text("Min Value:"), - ), - dmc.GridCol( - span=8, - children=dmc.NumberInput( - id=ElementIds.SEC2_MIN_VAL, - placeholder="Enter a number for the min val", - value=0, - step=1, - w="100%", - ), + dmc.Stack( + children=[ + dmc.Button( + "Apply filter", + id=ElementIds.SEC2_DATA_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + fullWidth=True, + ), + dmc.Flex( + children=[ + dmc.Text("Filter Variable:", miw=130), + dmc.Stack( + flex=1, + children=dropdown( + id=ElementIds.SEC2_DATA_FILTER_VAR, + options=explore_dropdown_names, + value=ColNames.RH, ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, - children=dmc.Text("Max Value:"), + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Min Value:", miw=130), + dmc.Stack( + flex=1, + children=dmc.NumberInput( + id=ElementIds.SEC2_MIN_VAL, + placeholder="Enter a number for the min val", + value=0, + step=1, ), - dmc.GridCol( - span=8, - children=dmc.NumberInput( - id=ElementIds.SEC2_MAX_VAL, - placeholder="Enter a number for the max val", - value=100, - step=1, - w="100%", - ), + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Max Value:", miw=130), + dmc.Stack( + flex=1, + children=dmc.NumberInput( + id=ElementIds.SEC2_MAX_VAL, + placeholder="Enter a number for the max val", + value=100, + step=1, ), - ], - ), - ], - ), + ), + ], + ), + ], ), ], ), @@ -453,22 +364,16 @@ def section_two(): """Return the two graphs in section two.""" return dmc.Stack( id=ElementIds.TAB6_SEC2_CONTAINER, - w="100%", - gap="md", - align="center", children=[ section_two_inputs(), dcc.Loading( type="circle", children=dmc.Paper( id=ElementIds.CUSTOM_HEATMAP, - radius="md", p="sm", - w="100%", ), ), dmc.Group( - gap="sm", children=[ dmc.CheckboxGroup( id=ElementIds.NORMALIZE, @@ -482,9 +387,7 @@ def section_two(): dcc.Loading( type="circle", children=dmc.Paper( - radius="md", p="sm", - w="100%", children=dcc.Graph( id=ElementIds.CUSTOM_SUMMARY, config=fig_config, @@ -498,234 +401,173 @@ def section_two(): def section_three_inputs(): """""" return dmc.Stack( - w="100%", - gap="md", children=[ - dmc.Grid( - gutter="md", + dmc.SimpleGrid( + cols={"base": 1, "md": 3}, children=[ - dmc.GridCol( - span=4, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, children=dmc.Text("X Variable:") - ), - dmc.GridCol( - span=8, - children=dropdown( - id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, - options=explore_dropdown_names, - value="DBT", - ), - ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, children=dmc.Text("Y Variable:") - ), - dmc.GridCol( - span=8, - children=dropdown( - id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, - options=explore_dropdown_names, - value=ColNames.RH, - ), + dmc.Stack( + children=[ + dmc.Flex( + children=[ + dmc.Text("X Variable:", miw=110, ml="md"), + dmc.Stack( + flex=1, + children=dropdown( + id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, + options=explore_dropdown_names, + value="DBT", ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, children=dmc.Text("Color By:") + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Y Variable:", miw=110, ml="md"), + dmc.Stack( + flex=1, + children=dropdown( + id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, + options=explore_dropdown_names, + value=ColNames.RH, ), - dmc.GridCol( - span=8, - children=dropdown( - id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, - options=explore_dropdown_names, - value="glob_hor_rad", - ), + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Color By:", miw=110, ml="md"), + dmc.Stack( + flex=1, + children=dropdown( + id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, + options=explore_dropdown_names, + value="glob_hor_rad", ), - ], - ), - ], - ), + ), + ], + ), + ], ), - dmc.GridCol( - span=4, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, - variant="filled", - color="blue", - size="md", - radius="md", - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=3, children=dmc.Text("Month Range") - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, - options=[ - { - "label": "Invert", - "value": "invert", - } - ], - value=[], - ), - ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=3, children=dmc.Text("Hour Range") - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), + dmc.Stack( + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + fullWidth=True, + ), + dmc.Flex( + children=[ + dmc.Text("Month Range", miw=110), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, - options=[ - { - "label": "Invert", - "value": "invert", - } - ], - value=[], - ), + ), + dcc.Checklist( + id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, + options=[ + {"label": "Invert", "value": "invert"} + ], + value=[], + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Hour Range", miw=110), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, ), - ], - ), - ], - ), + ), + dcc.Checklist( + id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, + options=[ + {"label": "Invert", "value": "invert"} + ], + value=[], + ), + ], + ), + ], ), - dmc.GridCol( - span=4, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Button( - "Apply filter", - id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, - variant="filled", - color="blue", - size="md", - radius="md", - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, - children=dmc.Text("Filter Variable:"), - ), - dmc.GridCol( - span=8, - children=dropdown( - id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, - options=explore_dropdown_names, - value=ColNames.RH, - ), - ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, children=dmc.Text("Min Value:") - ), - dmc.GridCol( - span=8, - children=dmc.NumberInput( - id=ElementIds.TAB6_SEC3_MIN_VAL, - placeholder="Enter a number for the min val", - value=0, - step=1, - w="100%", - ), + dmc.Stack( + children=[ + dmc.Button( + "Apply filter", + id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, + variant="filled", + color="blue", + size="md", + fullWidth=True, + ), + dmc.Flex( + children=[ + dmc.Text("Filter Variable:", miw=130), + dmc.Stack( + flex=1, + children=dropdown( + id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, + options=explore_dropdown_names, + value=ColNames.RH, ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, children=dmc.Text("Max Value:") + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Min Value:", miw=130), + dmc.Stack( + flex=1, + children=dmc.NumberInput( + id=ElementIds.TAB6_SEC3_MIN_VAL, + placeholder="Enter a number for the min val", + value=0, + step=1, ), - dmc.GridCol( - span=8, - children=dmc.NumberInput( - id=ElementIds.TAB6_SEC3_MAX_VAL, - placeholder="Enter a number for the max val", - value=100, - step=1, - w="100%", - ), + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Max Value:", miw=130), + dmc.Stack( + flex=1, + children=dmc.NumberInput( + id=ElementIds.TAB6_SEC3_MAX_VAL, + placeholder="Enter a number for the max val", + value=100, + step=1, ), - ], - ), - ], - ), + ), + ], + ), + ], ), ], ), @@ -736,8 +578,6 @@ def section_three_inputs(): def section_three(): """Return the two graphs in section three.""" return dmc.Stack( - w="100%", - gap="md", children=[ title_with_tooltip( text="More charts", @@ -749,18 +589,14 @@ def section_three(): type="circle", children=dmc.Paper( id=ElementIds.THREE_VAR, - radius="md", p="sm", - w="100%", ), ), dcc.Loading( type="circle", children=dmc.Paper( id=ElementIds.TWO_VAR, - radius="md", p="sm", - w="100%", ), ), ], @@ -770,7 +606,6 @@ def section_three(): def layout(): """Return the contents of tab six.""" return dmc.Box( - className="justify-center", children=[section_one(), section_two(), section_three()], ) diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py index b9f227de..63709a8e 100644 --- a/pages/lib/global_element_ids.py +++ b/pages/lib/global_element_ids.py @@ -155,7 +155,6 @@ class ElementIds(str, Enum): ID_WIND_META_STORE = "meta-store" ID_WIND_SI_IP_UNIT_STORE = "si-ip-unit-store" ID_WIND_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" - SEASONAL_WIND_ROSE_DOC = "seasonal-wind-rose-doc" LOADING_ONE = "loading-1" UPLOAD_DATA = "upload-data" UPLOAD_DATA_BUTTON = "upload-data-button" @@ -201,13 +200,9 @@ class ElementIds(str, Enum): WIND_PROFILE_GRAPH = "wind-profile-graph" TDB_PROFILE_GRAPH = "tdb-profile-graph" RH_PROFILE_GRAPH = "rh-profile-graph" - ALERT_CONTAINER = "alert-container" ID_LAYOUT_ALERT_AUTO = "alert-auto" - ID_MAIN_ALERT_AUTO = "alert-auto" ID_LAYOUT_INTERVAL_COMPONENT = "interval-component" - ID_MAIN_INTERVAL_COMPONENT = "interval-component" FOOTER_CONTAINER = "footer-container" - BANNER = "banner" BANNER_TITLE = "banner-title" ID_LAYOUT_BANNER_SUBTITLE = "banner-subtitle" ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT = "global-local-radio-input" @@ -218,20 +213,13 @@ class ElementIds(str, Enum): ID_LAYOUT_URL_STORE = "url-store" ID_LAYOUT_SI_IP_UNIT_STORE = "si-ip-unit-store" ID_LAYOUT_LINES_STORE = "lines-store" - TABS_CONTAINER = "tabs-container" - TABS_PARENT = "tabs-parent" - TABS = "tabs" - STORE_CONTAINER = "store-container" - TABS_CONTENT = "tabs-content" BURGER_BUTTON = "burger-button" - SIDE_BAR = "sidebar" + NAVBAR = "navbar" NAV_GROUP_MAIN = "nav-group-main" NAV_GROUP_CONTROLS = "nav-group-controls" NAV_DOC_LINK = "nav-doc-link" - LAYOUT_URL = "url" SELECT_URL = "url" MAIN_URL = "url" - PAGE_CONTENT = "page-content" NAV = "nav-" NAV_SUMMARY = "nav-summary" NAV_T_RH = "nav-t-rh" @@ -242,3 +230,6 @@ class ElementIds(str, Enum): NAV_OUTDOOR = "nav-outdoor" NAV_NATURAL_VENTILATION = "nav-natural-ventilation" NAV_CHANGELOG = "nav-changelog" + APP_SHELL = "appshell" + NAVBAR_CONTAINER = "navbar-container" + TOOLS_MENU_EXPANDED = "tools-menu-expanded" diff --git a/pages/lib/layout.py b/pages/lib/layout.py index e47b9eea..d6891777 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -5,279 +5,40 @@ from pages.lib.global_column_names import ColNames from config import DocLinks, UnitSystem from pages.lib.global_element_ids import ElementIds -from pages.lib.page_icon import PageIcon -def burger_button(): - """create burger button""" - return dmc.ActionIcon( - DashIconify(icon="radix-icons:hamburger-menu", width=20), - id=ElementIds.BURGER_BUTTON, - size="lg", - variant="filled", - color="blue", - ) - - -def alert(): - """Survey toast + periodic timer.""" - return dmc.Stack( - gap=0, - children=[ - dmc.Alert( - [ - "If you have a moment, help us improve Clima and take a ", - dmc.Anchor( - "quick user survey", - href="https://forms.gle/k289zP3R92jdu14M7", - target="_blank", - c="white", - underline=True, - ), - "! ☀️", - ], - id=ElementIds.ID_LAYOUT_ALERT_AUTO, - title="CBE Clima User Survey", - icon="Info", - color="blue", - variant="filled", - withCloseButton=True, - pos="fixed", - top="25px", - right="10px", - w="400px", - style={"zIndex": 1002, "display": "none"}, - ), - dcc.Interval( - id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, - interval=12 * 1000, - n_intervals=0, - ), - ], - ) - - -def footer(): - """Build the footer at the bottom of the page.""" - white_anchor_style = { - "underline": True, - "c": "white", - "fz": "md", - "fw": 500, - "target": "_blank", +class NavBarIcons: + _ICON_MAP = { + "Select Weather File": "tabler:upload", + "Climate Summary": "tabler:chart-bar", + "Temperature and Humidity": "tabler:temperature", + "Sun and Clouds": "tabler:sun", + "Wind": "tabler:wind", + "Psychrometric Chart": "tabler:chart-dots", + "Natural Ventilation": "tabler:windmill", + "Outdoor Comfort": "tabler:thermometer", + "Data Explorer": "tabler:database", + "Changelog": "tabler:history", } - footer_links = [ - ( - "Version: 0.9.0", - "https://center-for-the-built-environment.gitbook.io/clima/version/changelog", - ), - ("Contributors", "https://cbe-berkeley.gitbook.io/clima/#contributions"), - ( - "Report issues on GitHub", - "https://github.com/CenterForTheBuiltEnvironment/clima/issues", - ), - ( - "Contact us", - "https://github.com/CenterForTheBuiltEnvironment/clima/discussions", - ), - ("Documentation", "https://center-for-the-built-environment.gitbook.io/clima/"), - ( - "License", - "https://center-for-the-built-environment.gitbook.io/clima/#license", - ), - ] - - return dmc.Box( - id=ElementIds.FOOTER_CONTAINER, - p="md", - m=0, - c="white", - bg="#003262", - display="flex", - w="100%", - style={ - "flexWrap": "nowrap", - "minHeight": "fit-content", - "alignItems": "flex-start", - }, - children=[ - # Logo section - dmc.Box( - children=[ - dmc.Anchor( - href="https://cbe.berkeley.edu/", - children=dmc.Image( - src="assets/img/cbe-logo.png", - alt="CBE Logo", - h=65, - w="auto", - fit="contain", - ), - ), - ], - flex="0 0 33.333333%", - maw="33.333333%", - p="30px 15px 10px 25px", - display="flex", - style={"justifyContent": "flex-start", "alignItems": "flex-start"}, - ), - # Content section - dmc.Box( - children=[ - dmc.Stack( - gap="xs", - children=[ - dcc.Markdown( - """ - Please cite us: - Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: - A free and open-source web application for climate analysis tailored to sustainable building design. - Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). - """, - style={ - "fontSize": "16px", - "lineHeight": 1.5, - "fontWeight": 500, - "color": "white", - }, - ), - dmc.Group( - [ - dmc.Anchor(text, href=url, **white_anchor_style) - for text, url in footer_links - ], - gap="sm", - mt="md", - ), - ], - mt="md", - ), - ], - flex="0 0 66.666667%", - maw="66.666667%", - p="0px 15px 10px 15px", - display="flex", - style={"justifyContent": "flex-start", "alignItems": "flex-start"}, - ), - ], - ) + SELECT_WEATHER_FILE = _ICON_MAP["Select Weather File"] + CLIMATE_SUMMARY = _ICON_MAP["Climate Summary"] + TEMPERATURE_AND_HUMIDITY = _ICON_MAP["Temperature and Humidity"] + SUN_AND_CLOUDS = _ICON_MAP["Sun and Clouds"] + WIND = _ICON_MAP["Wind"] + PSYCHROMETRIC_CHART = _ICON_MAP["Psychrometric Chart"] + NATURAL_VENTILATION = _ICON_MAP["Natural Ventilation"] + OUTDOOR_COMFORT = _ICON_MAP["Outdoor Comfort"] + DATA_EXPLORER = _ICON_MAP["Data Explorer"] + CHANGELOG = _ICON_MAP["Changelog"] + @classmethod + def get_icon(cls, page_name): + """Get icon for a page name.""" + return cls._ICON_MAP.get(page_name, "tabler:circle") -def banner(): - """Top banner rewritten with dash-mantine-components only.""" - return dmc.Box( - id=ElementIds.BANNER, - p="md", - bg="#003262", - c="white", - pos="relative", - style={"zIndex": 1}, - children=[ - dmc.Group( - justify="space-between", - align="center", - wrap="nowrap", - children=[ - dmc.Group( - align="center", - gap="md", - children=[ - burger_button(), - dmc.Image( - src="assets/img/cbe-logo-small.png", h=40, w="auto" - ), - dmc.Stack( - gap=2, - children=[ - dmc.Title( - "CBE Clima Tool", - id=ElementIds.BANNER_TITLE, - order=2, - fw=500, - ff="'Open Sans', sans-serif", - lh=1.1, - c="white", - ), - dmc.Text( - "Current Location: N/A", - id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE, - size="sm", - opacity=0.85, - ff="'Poppins', sans-serif", - fw=400, - h=25, - style={"overflow": "hidden"}, - c="white", - ), - ], - ), - ], - ), - ], - ) - ], - ) - -def sidebar(): - """create sidebar""" - return dmc.Drawer( - id=ElementIds.SIDE_BAR, - title=dmc.Group( - [ - dmc.Image(src="assets/img/cbe-logo-small.png", h=30, w="auto"), - dmc.Text("CBE Clima Tool", fw=600), - ] - ), - size="300px", - zIndex=1001, - opened=False, - styles={ - "content": { - "top": "80px", - "left": 0, - "position": "fixed", - "borderRadius": "0 8px 8px 0", - "boxShadow": "2px 0 8px rgba(0,0,0,0.1)", - "backgroundColor": "#f8f9fa", - "padding": "16px", - }, - "overlay": { - "top": "80px", - "left": 0, - "height": "calc(100vh - 80px)", - "position": "fixed", - }, - "header": { - "borderBottom": "1px solid #e9ecef", - "paddingBottom": "12px", - "marginBottom": "16px", - "position": "sticky", - "top": 0, - "backgroundColor": "#f8f9fa", - "zIndex": 1002, - }, - "title": { - "fontWeight": 600, - "fontSize": "18px", - "paddingRight": 30, - "position": "relative", - "zIndex": 1001, - }, - "body": { - "padding": 0, - "overflowY": "auto", - "maxHeight": "calc(100vh - 80px)", - "position": "relative", - "zIndex": 1, - }, - }, - children=[dmc.Stack(gap=0, children=build_sidebar_nav_items())], - ) - - -def build_sidebar_nav_items(): +def create_navbar(): nav_link_styles = { "root": { "borderRadius": "6px", @@ -295,31 +56,28 @@ def build_sidebar_nav_items(): } } - # === Secondary Menu === + # Secondary Menu sub_links = [ dmc.NavLink( label=page[ColNames.NAME], leftSection=DashIconify( - icon=PageIcon.get_icon(page[ColNames.NAME]), width=20 + icon=NavBarIcons.get_icon(page[ColNames.NAME]), width=20 ), href=page[ColNames.PATH], id=f"nav-{page[ColNames.PATH].replace('/', '')}", active=False, - mb="xs", + mb="2px", styles=nav_link_styles, ) for page in dash.page_registry.values() if page[ColNames.NAME] not in ["404"] ] - # Primary Menu parent_group = dmc.NavLink( label="Pages Menu", - leftSection=DashIconify(icon="tabler:list-details", width=20), children=sub_links, id=ElementIds.NAV_GROUP_MAIN, variant="light", - childrenOffset=18, ) segmented_control_styles = { @@ -328,60 +86,211 @@ def build_sidebar_nav_items(): } controls_stack = dmc.Stack( - gap="sm", - px=0, + gap="xs", py="xs", children=[ dmc.SegmentedControl( id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT, value="local", + color="blue", data=[ {"label": "Global Value Ranges", "value": "global"}, {"label": "Local Value Ranges", "value": "local"}, ], radius="md", size="sm", - w="100%", styles=segmented_control_styles, ), dmc.SegmentedControl( id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT, value=UnitSystem.SI, + color="blue", data=[ {"label": UnitSystem.SI.upper(), "value": UnitSystem.SI}, {"label": UnitSystem.IP.upper(), "value": UnitSystem.IP}, ], radius="md", size="sm", - w="100%", styles=segmented_control_styles, ), ], ) - # Primary Menu + # Tools controls_group = dmc.NavLink( label="Tools Menu", - leftSection=DashIconify(icon="tabler:settings", width=20), children=[controls_stack], id=ElementIds.NAV_GROUP_CONTROLS, variant="light", - childrenOffset=18, + childrenOffset=8, ) - # Primary Menu - Documentation + # Documentation doc_link = dmc.NavLink( label="Documentation", - leftSection=DashIconify(icon="tabler:file-text", width=20), href=DocLinks.MAIN.value, target="_blank", id=ElementIds.NAV_DOC_LINK, variant="light", ) - return [parent_group, controls_group, doc_link] + + return dmc.ScrollArea( + children=dmc.Stack(gap="xs", children=[parent_group, controls_group, doc_link]), + pr="xs", + ) + + +def create_header(): + return dmc.Group( + [ + dmc.Burger( + id=ElementIds.BURGER_BUTTON, + size="sm", + opened=True, + color="blue", + ), + dmc.Anchor( + href="/", + children=dmc.Image(src="assets/img/cbe-logo-small.png", h=40, flex=0), + ), + dmc.Stack( + gap="xs", + children=[ + dmc.Title( + "CBE Clima Tool", + id=ElementIds.BANNER_TITLE, + order=2, + fw=500, + ff="'Open Sans', sans-serif", + lh=1.1, + c="white", + ), + dmc.Text( + "Current Location: N/A", + id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE, + size="sm", + opacity=0.85, + ff="'Poppins', sans-serif", + fw=400, + h=25, + style={"overflow": "hidden"}, + c="white", + ), + ], + ), + dmc.Alert( + [ + "If you have a moment, help us improve Clima and take a ", + dmc.Anchor( + "quick user survey", + href="https://forms.gle/k289zP3R92jdu14M7", + target="_blank", + c="white", + underline=True, + ), + "! ☀️", + ], + id=ElementIds.ID_LAYOUT_ALERT_AUTO, + title="CBE Clima User Survey", + icon=dmc.ThemeIcon( + DashIconify(icon="tabler:info-circle", color="white"), + ), + color="blue", + variant="filled", + withCloseButton=True, + style={"display": "none"}, + ), + ], + h="100%", + px="md", + ) + + +def create_footer(): + white_anchor_style = { + "underline": True, + "c": "white", + "fz": "md", + "fw": 500, + "target": "_blank", + } + + footer_links = [ + ( + "Version: 0.9.0", + "https://center-for-the-built-environment.gitbook.io/clima/version/changelog", + ), + ("Contributors", "https://cbe-berkeley.gitbook.io/clima/#contributions"), + ( + "Report issues on GitHub", + "https://github.com/CenterForTheBuiltEnvironment/clima/issues", + ), + ( + "Contact us", + "https://github.com/CenterForTheBuiltEnvironment/clima/discussions", + ), + ("Documentation", "https://center-for-the-built-environment.gitbook.io/clima/"), + ( + "License", + "https://center-for-the-built-environment.gitbook.io/clima/#license", + ), + ] + + return dmc.Group( + [ + dmc.Anchor( + href="https://cbe.berkeley.edu/", + children=dmc.Image( + src="assets/img/cbe-logo.png", + alt="CBE Logo", + h=40, + w="auto", + fit="contain", + ), + ), + dmc.Stack( + gap="xs", + children=[ + dcc.Markdown( + """ + Please cite us: Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). + """, + style={ + "fontSize": "16px", + "lineHeight": 1.3, + "fontWeight": 400, + "color": "white", + "textAlign": "left", + }, + ), + dmc.Group( + [ + dmc.Anchor(text, href=url, **white_anchor_style) + for text, url in footer_links + ], + gap="sm", + wrap="wrap", + justify="flex-start", + ), + ], + flex=1, + align="flex-start", + ml="xl", + ), + ], + id=ElementIds.FOOTER_CONTAINER, + p="sm", + c="white", + bg="#003262", + h="100%", + gap="xl", + justify="flex-start", + align="center", + px="lg", + ) -def store(): +def create_stores(): return dmc.Box( id=ElementIds.STORE, children=[ @@ -390,60 +299,106 @@ def store(): dcc.Store(id=ElementIds.ID_LAYOUT_URL_STORE, storage_type="session"), dcc.Store(id=ElementIds.ID_LAYOUT_SI_IP_UNIT_STORE, storage_type="session"), dcc.Store(id=ElementIds.ID_LAYOUT_LINES_STORE, storage_type="session"), + dcc.Store( + id=ElementIds.TOOLS_MENU_EXPANDED, data=False, storage_type="session" + ), + dcc.Interval( + id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, + interval=12 * 1000, + n_intervals=0, + ), ], ) -def build_tabs(): - return dmc.Box( - id=ElementIds.TABS_CONTAINER, - m=0, - mt=0, - children=[ - store(), - dmc.Box( - id=ElementIds.TABS_CONTENT, +def create_collapsible_layout(): + return dmc.AppShell( + [ + dmc.AppShellHeader( + create_header(), + bg="#003262", + ), + dmc.AppShellNavbar( + id=ElementIds.NAVBAR, + children=create_navbar(), p="md", - children=[ - alert(), - dash.page_container, - ], + bg="#f8f9fa", + ), + # including main and footer + dmc.AppShellMain( + dmc.ScrollArea( + children=[ + create_stores(), + dash.page_container, + create_footer(), + ], + ), + pos="relative", + style={ + "zIndex": 1, + "@media (max-width: 768px)": { + "left": "0px", + }, + }, ), ], + header={"height": 80}, + navbar={ + "width": 180, + "breakpoint": "sm", + "collapsed": {"mobile": True, "desktop": False}, + "id": ElementIds.NAVBAR_CONTAINER, + }, + padding="md", + id=ElementIds.APP_SHELL, ) @callback( - Output(ElementIds.SIDE_BAR, "opened"), - Input(ElementIds.BURGER_BUTTON, "n_clicks"), - State(ElementIds.SIDE_BAR, "opened"), - prevent_initial_call=True, + [ + Output(ElementIds.APP_SHELL, "navbar"), + Output(ElementIds.TOOLS_MENU_EXPANDED, "data"), + ], + [ + Input(ElementIds.BURGER_BUTTON, "opened"), + Input(ElementIds.NAV_GROUP_CONTROLS, "opened"), + Input(ElementIds.NAV_GROUP_MAIN, "opened"), + ], + [ + State(ElementIds.APP_SHELL, "navbar"), + State(ElementIds.TOOLS_MENU_EXPANDED, "data"), + ], ) -def toggle_sidebar(n_clicks, opened): - return not opened if n_clicks else opened - +def toggle_navbar_and_width( + burger_opened, tools_opened, pages_opened, navbar, tools_expanded +): + navbar["collapsed"] = {"mobile": not burger_opened, "desktop": not burger_opened} + + WIDTHS = {"default": 180, "pages": 300, "tools": 400} + + if tools_opened is not None: + tools_expanded = tools_opened + navbar["width"] = ( + WIDTHS["tools"] + if tools_opened + else (WIDTHS["pages"] if pages_opened else WIDTHS["default"]) + ) + elif pages_opened is not None: + navbar["width"] = WIDTHS["pages"] if pages_opened else WIDTHS["default"] -@callback( - Output(ElementIds.SIDE_BAR, "opened", allow_duplicate=True), - Input(ElementIds.LAYOUT_URL, "pathname"), - prevent_initial_call="initial_duplicate", -) -def close_sidebar_on_navigation(pathname): - return False + return navbar, tools_expanded -# Callback to set active state for navigation links based on current URL @callback( [ Output(f"nav-{page[ColNames.PATH].replace('/', '')}", "active") for page in dash.page_registry.values() if page[ColNames.NAME] not in ["404"] ], - Input(ElementIds.LAYOUT_URL, "pathname"), + Input(ElementIds.MAIN_URL, "pathname"), prevent_initial_call=True, ) def update_nav_active_state(pathname): - """Update active state of navigation links based on current URL pathname""" return [ pathname == page[ColNames.PATH] for page in dash.page_registry.values() @@ -457,7 +412,6 @@ def update_nav_active_state(pathname): prevent_initial_call=True, ) def show_alert_after_delay(n_intervals): - """Show alert after 6 seconds, then hide after 5 more seconds""" base_style = { "position": "fixed", "top": "25px", @@ -466,7 +420,6 @@ def show_alert_after_delay(n_intervals): "zIndex": 1002, } - # Determine display status based on the number of intervals if n_intervals == 1: return {**base_style, "display": "block"} else: diff --git a/pages/lib/page_icon.py b/pages/lib/page_icon.py deleted file mode 100644 index 6515a6b1..00000000 --- a/pages/lib/page_icon.py +++ /dev/null @@ -1,32 +0,0 @@ -class PageIcon: - """Page icon mappings - optimized with dictionary.""" - - # Page Name to icon Mapping - _ICON_MAP = { - "Select Weather File": "tabler:upload", - "Climate Summary": "tabler:chart-bar", - "Temperature and Humidity": "tabler:temperature", - "Sun and Clouds": "tabler:sun", - "Wind": "tabler:wind", - "Psychrometric Chart": "tabler:chart-dots", - "Natural Ventilation": "tabler:windmill", - "Outdoor Comfort": "tabler:thermometer", - "Data Explorer": "tabler:database", - "Changelog": "tabler:history", - } - - SELECT_WEATHER_FILE = _ICON_MAP["Select Weather File"] - CLIMATE_SUMMARY = _ICON_MAP["Climate Summary"] - TEMPERATURE_AND_HUMIDITY = _ICON_MAP["Temperature and Humidity"] - SUN_AND_CLOUDS = _ICON_MAP["Sun and Clouds"] - WIND = _ICON_MAP["Wind"] - PSYCHROMETRIC_CHART = _ICON_MAP["Psychrometric Chart"] - NATURAL_VENTILATION = _ICON_MAP["Natural Ventilation"] - OUTDOOR_COMFORT = _ICON_MAP["Outdoor Comfort"] - DATA_EXPLORER = _ICON_MAP["Data Explorer"] - CHANGELOG = _ICON_MAP["Changelog"] - - @classmethod - def get_icon(cls, page_name): - """Get icon for a page name.""" - return cls._ICON_MAP.get(page_name, "tabler:circle") diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 8e534d90..53d9c26f 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -149,7 +149,6 @@ def generate_custom_inputs_psy( def title_with_tooltip(text, tooltip_text, id_button): if tooltip_text: return dmc.Group( - gap="xs", align="center", mt="md", px="md", @@ -172,15 +171,7 @@ def title_with_tooltip(text, tooltip_text, id_button): ], ) else: - return dmc.Group( - gap="xs", - align="center", - mt="md", - px="md", - children=[ - dmc.Title(text, order=3), - ], - ) + return dmc.Title(text, order=3, mt="md", px="md") def title_with_link( @@ -190,7 +181,6 @@ def title_with_link( doc_link: str = "", ): return dmc.Group( - gap="xs", align="center", mt="md", px="md", diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 0c2d210f..4499cc8d 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -39,7 +39,7 @@ def layout(): - return dmc.Stack(id=ElementIds.MAIN_NV_SECTION, gap="md") + return dmc.Stack(id=ElementIds.MAIN_NV_SECTION) @callback( @@ -67,14 +67,11 @@ def update_layout(si_ip): type="circle", children=dmc.Paper( id=ElementIds.NV_HEATMAP_CHART, - p="md", - mt="md", + p="sm", ), ), dmc.Group( - align="center", justify="center", - gap="sm", children=[ dmc.Switch( id=ElementIds.SWITCHES_INPUT, @@ -98,198 +95,161 @@ def update_layout(si_ip): type="circle", children=dmc.Paper( id=ElementIds.NV_BAR_CHART, - p="md", - mt="md", + p="sm", ), ), ] def inputs_tab(t_min, t_max, d_set): - return dmc.Grid( - gutter="xl", + return dmc.SimpleGrid( + cols={"base": 1, "md": 3}, children=[ - dmc.GridCol( - span=4, - children=dmc.Stack( - children=[ - dmc.Button( - "Apply filter", - color="primary", - id=ElementIds.NV_DBT_FILTER, - variant="link", - size="md", - fullWidth=True, - n_clicks=1, - ), - dmc.Text( - "Outdoor dry-bulb air temperature range", - size="md", - ), - dmc.Group( - gap="xl", - grow=True, - children=[ - dmc.Text("Min Value:", size="md"), - dmc.NumberInput( - id=ElementIds.NV_TDB_MIN_VAL, - placeholder="Enter a number for the min val", + dmc.Stack( + children=[ + dmc.Button( + "Apply filter", + color="primary", + id=ElementIds.NV_DBT_FILTER, + variant="link", + size="md", + fullWidth=True, + n_clicks=1, + ), + dmc.Text( + "Outdoor dry-bulb air temperature range", + size="md", + ml="md", + ), + dmc.Flex( + children=[ + dmc.Text("Min Value:", size="md", ml="md"), + dmc.NumberInput( + id=ElementIds.NV_TDB_MIN_VAL, + placeholder="Enter a number for the min val", + step=1, + value=t_min, + ), + ], + ), + # Max + dmc.Flex( + children=[ + dmc.Text("Max Value:", size="md", ml="md"), + dmc.NumberInput( + id=ElementIds.NV_TDB_MAX_VAL, + placeholder="Enter a number for the max val", + value=t_max, + step=1, + ), + ], + ), + ], + ), + dmc.Stack( + children=[ + dmc.Button( + "Apply month and hour filter", + color="primary", + id=ElementIds.NV_MONTH_HOUR_FILTER, + variant="link", + size="md", + fullWidth=True, + ), + dmc.Flex( + children=[ + dmc.Text("Month Range", size="md", miw=120), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.NV_MONTH_SLIDER, + min=1, + max=12, step=1, - value=t_min, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, ), - ], - ), - dmc.Group( - gap="xl", - grow=True, - children=[ - dmc.Text("Max Value:", size="md"), - dmc.NumberInput( - id=ElementIds.NV_TDB_MAX_VAL, - placeholder="Enter a number for the max val", - value=t_max, + ), + dcc.Checklist( + options=[{"label": "Invert", "value": "invert"}], + value=[], + id=ElementIds.INVERT_MONTH_NV, + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Hour Range", size="md", miw=120), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.NV_HOUR_SLIDER, + min=0, + max=24, step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, ), - ], - ), - ], - ), - ), - dmc.GridCol( - span=4, - children=dmc.Stack( - children=[ - dmc.Button( - "Apply month and hour filter", - color="primary", - id=ElementIds.NV_MONTH_HOUR_FILTER, - variant="link", - size="md", - fullWidth=True, - radius="sm", - ), - dmc.Grid( - align="center", - gutter="sm", - children=[ - dmc.GridCol( - span=3, - children=dmc.Text("Month Range", size="md"), - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.NV_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_MONTH_NV, - ), - ), - ], - ), - dmc.Grid( - align="center", - gutter="sm", - children=[ - dmc.GridCol( - span=3, - children=dmc.Text("Hour Range", size="md"), - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.NV_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, - ), - ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - options=[ - {"label": "Invert", "value": "invert"}, - ], - value=[], - id=ElementIds.INVERT_HOUR_NV, - ), - ), - ], - ), - ], - ), + ), + dcc.Checklist( + options=[{"label": "Invert", "value": "invert"}], + value=[], + id=ElementIds.INVERT_HOUR_NV, + ), + ], + ), + ], ), - dmc.GridCol( - span=4, - children=dmc.Stack( - children=[ - dmc.Button( - "Apply filter", - color="primary", - id=ElementIds.NV_DPT_FILTER, - mb="xs", - variant="link", - size="md", - fullWidth=True, - n_clicks=0, - disabled=True, - ), - dcc.Checklist( - options=[ - { - "label": ( - "Avoid condensation with radiant systems: If the" - " outdoor dew point temperature is below the" - " radiant system surface temperature, the data" - " point is not plot." - ), - "value": 1, - }, - ], - value=[], - id=ElementIds.ENABLE_CONDENSATION, - ), - dmc.Group( - align="center", - gap="sm", - grow=True, - children=[ - dmc.Text("Surface temperature:", size="md"), - dmc.NumberInput( - id=ElementIds.NV_DPT_MAX_VAL, - placeholder="Enter a number for the max val", - value=d_set, - step=1, - w="50%", + dmc.Stack( + children=[ + dmc.Button( + "Apply filter", + color="primary", + id=ElementIds.NV_DPT_FILTER, + mb="xs", + variant="link", + size="md", + fullWidth=True, + n_clicks=0, + disabled=True, + ), + dcc.Checklist( + options=[ + { + "label": ( + "Avoid condensation with radiant systems: If the " + "outdoor dew point temperature is below the radiant " + "system surface temperature, the data point is not plot." ), - ], - ), - ], - ), + "value": 1, + }, + ], + value=[], + id=ElementIds.ENABLE_CONDENSATION, + ), + dmc.Flex( + children=[ + dmc.Text("Surface temperature:", size="md"), + dmc.NumberInput( + id=ElementIds.NV_DPT_MAX_VAL, + placeholder="Enter a number for the max val", + value=d_set, + step=1, + w="50%", + ), + ], + ), + ], ), ], ) diff --git a/pages/outdoor.py b/pages/outdoor.py index d3cececb..fcc814f4 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -36,115 +36,94 @@ def inputs_outdoor_comfort(): - return dmc.Grid( - gutter="md", + return dmc.SimpleGrid( + cols={"base": 1, "md": 2}, children=[ - dmc.GridCol( - span=6, - children=dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol(span=3, children=dmc.Text("Select a scenario:")), - dmc.GridCol( - span=6, - children=dropdown( - id=ElementIds.TAB7_DROPDOWN, - options=outdoor_dropdown_names, - value="utci_Sun_Wind", - persistence=True, - persistence_type="session", + dmc.Stack( + children=[ + dmc.Flex( + children=[ + dmc.Text("Select a scenario:", miw=140, ml="md"), + dmc.Stack( + flex=1, + children=dropdown( + id=ElementIds.TAB7_DROPDOWN, + options=outdoor_dropdown_names, + value="utci_Sun_Wind", + persistence=True, + persistence_type="session", + ), ), - ), - dmc.GridCol( - span=3, - children=dmc.Paper(id=ElementIds.IMAGE_SELECTION), - ), - ], - ), + dmc.Center( + children=dmc.Paper(id=ElementIds.IMAGE_SELECTION) + ), + ], + ), + ], ), - dmc.GridCol( - span=6, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, - variant="filled", - color="blue", - size="md", - radius="md", - w="100%", - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol(span=2, children=dmc.Text("Month Range")), - dmc.GridCol( - span=7, - children=dcc.RangeSlider( - id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], - ), - ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol(span=2, children=dmc.Text("Hour Range")), - dmc.GridCol( - span=7, - children=dcc.RangeSlider( - id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, - ), + dmc.Stack( + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, + variant="filled", + color="blue", + size="md", + fullWidth=True, + ), + dmc.Flex( + children=[ + dmc.Text("Month Range", miw=120), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], - ), + ), + dcc.Checklist( + id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, + options=[{"label": "Invert", "value": "invert"}], + value=[], + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Hour Range", miw=120), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, ), - ], - ), - ], - ), + ), + dcc.Checklist( + id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, + options=[{"label": "Invert", "value": "invert"}], + value=[], + ), + ], + ), + ], ), ], ) @@ -152,14 +131,10 @@ def inputs_outdoor_comfort(): def outdoor_comfort_chart(): return dmc.Stack( - w="100%", - gap="md", children=[ dmc.Paper( id=ElementIds.OUTDOOR_COMFORT_OUTPUT, - radius="md", p="sm", - w="100%", ), # UTCI heatmap chart title_with_link( @@ -171,10 +146,7 @@ def outdoor_comfort_chart(): type="circle", children=dmc.Paper( id=ElementIds.UTCI_HEATMAP, - radius="md", p="sm", - w="100%", - h=400, ), ), # UTCI thermal stress chart @@ -187,21 +159,15 @@ def outdoor_comfort_chart(): type="circle", children=dmc.Paper( id=ElementIds.UTCI_CATEGORY_HEATMAP, - radius="md", p="sm", - w="100%", - h=400, ), ), # Normalize data dmc.Group( - align="center", justify="center", - gap="sm", children=[ dmc.Switch( id=ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, - label="", checked=True, size="md", color="blue", @@ -221,9 +187,7 @@ def outdoor_comfort_chart(): type="circle", children=dmc.Paper( id=ElementIds.UTCI_SUMMARY_CHART, - radius="md", p="sm", - w="100%", ), ), ], @@ -232,14 +196,11 @@ def outdoor_comfort_chart(): def layout(): return dmc.Stack( - w="100%", - gap="md", + mt="md", children=[ dcc.Loading( type="circle", children=dmc.Stack( - w="100%", - gap="md", children=[ inputs_outdoor_comfort(), outdoor_comfort_chart(), diff --git a/pages/psy-chart.py b/pages/psy-chart.py index a2dfa61b..1793e076 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -59,202 +59,150 @@ def inputs(): """""" return dmc.Stack( - w="100%", - gap="md", children=[ - dmc.Grid( - gutter="md", + dmc.SimpleGrid( + cols={"base": 1, "md": 3}, + spacing="md", children=[ - dmc.GridCol( - span=4, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, children=dmc.Text("Color By:") - ), - dmc.GridCol( - span=8, - children=dropdown( - id=ElementIds.PSY_COLOR_BY_DROPDOWN, - options=psy_dropdown_names, - value="Frequency", - persistence=True, - persistence_type="session", - ), - ), - ], + dmc.Flex( + align="center", + mt="md", + children=[ + dmc.Text("Color By:", miw=110), + dmc.Stack( + flex=1, + children=dropdown( + id=ElementIds.PSY_COLOR_BY_DROPDOWN, + options=psy_dropdown_names, + value="Frequency", + persistence=True, + persistence_type="session", ), - ], - ), + ), + ], ), - dmc.GridCol( - span=4, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.MONTH_HOUR_FILTER, - variant="filled", - color="blue", - size="md", - radius="md", - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=3, children=dmc.Text("Month Range") - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.PSY_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), + dmc.Stack( + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.MONTH_HOUR_FILTER, + variant="filled", + color="blue", + size="md", + fullWidth=True, + ), + dmc.Flex( + children=[ + dmc.Text("Month Range", miw=110), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.PSY_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - id=ElementIds.INVERT_MONTH_PSY, - options=[ - { - "label": "Invert", - "value": "invert", - } - ], - value=[], - ), + ), + dcc.Checklist( + id=ElementIds.INVERT_MONTH_PSY, + options=[ + {"label": "Invert", "value": "invert"} + ], + value=[], + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Hour Range", miw=110), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.PSY_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=3, children=dmc.Text("Hour Range") - ), - dmc.GridCol( - span=6, - children=dcc.RangeSlider( - id=ElementIds.PSY_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, - ), - ), - dmc.GridCol( - span=3, - children=dcc.Checklist( - id=ElementIds.INVERT_HOUR_PSY, - options=[ - { - "label": "Invert", - "value": "invert", - } - ], - value=[], - ), - ), - ], - ), - ], - ), + ), + dcc.Checklist( + id=ElementIds.INVERT_HOUR_PSY, + options=[ + {"label": "Invert", "value": "invert"} + ], + value=[], + ), + ], + ), + ], ), - dmc.GridCol( - span=4, - children=dmc.Stack( - gap="sm", - children=[ - dmc.Button( - "Apply filter", - id=ElementIds.DATA_FILTER, - variant="filled", - color="blue", - size="md", - radius="md", - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, - children=dmc.Text("Filter Variable:"), + dmc.Stack( + children=[ + dmc.Button( + "Apply filter", + id=ElementIds.DATA_FILTER, + variant="filled", + color="blue", + size="md", + fullWidth=True, + ), + dmc.Flex( + children=[ + dmc.Text("Filter Variable:", miw=130), + dmc.Stack( + flex=1, + children=dropdown( + id=ElementIds.PSY_VAR_DROPDOWN, + options=dropdown_names, + value=ColNames.RH, ), - dmc.GridCol( - span=8, - children=dropdown( - id=ElementIds.PSY_VAR_DROPDOWN, - options=dropdown_names, - value=ColNames.RH, - ), + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Min Value:", miw=130), + dmc.Stack( + flex=1, + children=dmc.NumberInput( + id=ElementIds.PSY_MIN_VAL, + placeholder="Enter a number for the min val", + value=0, + step=1, + size="md", ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, children=dmc.Text("Min Value:") + ), + ], + ), + dmc.Flex( + children=[ + dmc.Text("Max Value:", miw=130), + dmc.Stack( + flex=1, + children=dmc.NumberInput( + id=ElementIds.PSY_MAX_VAL, + placeholder="Enter a number for the max val", + value=100, + step=1, + size="md", ), - dmc.GridCol( - span=8, - children=dmc.NumberInput( - id=ElementIds.PSY_MIN_VAL, - placeholder="Enter a number for the min val", - value=0, - step=1, - w="100%", - ), - ), - ], - ), - dmc.Grid( - gutter="sm", - align="center", - children=[ - dmc.GridCol( - span=4, children=dmc.Text("Max Value:") - ), - dmc.GridCol( - span=8, - children=dmc.NumberInput( - id=ElementIds.PSY_MAX_VAL, - placeholder="Enter a number for the max val", - value=100, - step=1, - w="100%", - ), - ), - ], - ), - ], - ), + ), + ], + ), + ], ), ], ), @@ -264,8 +212,6 @@ def inputs(): def layout(): return dmc.Stack( - w="100%", - gap="md", children=[ title_with_link( text="Psychrometric Chart", @@ -275,15 +221,11 @@ def layout(): dcc.Loading( type="circle", children=dmc.Stack( - w="100%", - gap="md", children=[ inputs(), dmc.Paper( id=ElementIds.PSYCH_CHART, - radius="md", p="sm", - w="100%", ), ], ), diff --git a/pages/select.py b/pages/select.py index 738a6818..4a8e2a4f 100644 --- a/pages/select.py +++ b/pages/select.py @@ -56,7 +56,6 @@ def layout(): id=ElementIds.UPLOAD_DATA_BUTTON, variant="outline", color="gray", - radius="sm", style={"borderStyle": "dashed", "borderRadius": "5px"}, styles={"label": {"fontWeight": 400}}, ), @@ -102,13 +101,10 @@ def layout(): ), ], justify="flex-end", - gap="md", - w="100%", ), ], ), ], - w="100%", mx=0, px=0, py="md", diff --git a/pages/summary.py b/pages/summary.py index 8dc257ea..f96f237d 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -1,10 +1,11 @@ import dash -from dash.exceptions import PreventUpdate -from dash_extensions.enrich import dcc, Output, Input, State, callback +import pandas as pd import dash_mantine_components as dmc import plotly.graph_objects as go import requests +from dash.exceptions import PreventUpdate +from dash_extensions.enrich import dcc, Output, Input, State, callback from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.charts_summary import world_map from pages.lib.extract_df import get_data @@ -38,7 +39,7 @@ def layout(): fluid=True, px="md", children=[ - dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER, gap="md"), + dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER), ], ) @@ -57,19 +58,17 @@ def update_layout(si_ip): return dmc.Stack( id=ElementIds.TAB2_SCE1_CONTAINER, - gap="xl", children=[ dcc.Loading( type="circle", children=dmc.Stack( id=ElementIds.LOCATION_INFO, - gap="xs", - p="md", + p="sm", ), ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.WORLD_MAP, gap=0), + children=dmc.Stack(id=ElementIds.WORLD_MAP), ), title_with_tooltip( text="Download", @@ -81,7 +80,6 @@ def update_layout(si_ip): children=dmc.Group( align="center", justify="flex-start", - gap="md", children=[ dmc.Button( "Download EPW", @@ -105,11 +103,10 @@ def update_layout(si_ip): id_button=IdButtons.HDD_CDD_CHART, doc_link=DocLinks.DEGREE_DAYS, ), - dmc.Stack(id=ElementIds.WARNING_CDD_HIGHER_HDD, gap=0), + dmc.Stack(id=ElementIds.WARNING_CDD_HIGHER_HDD), dmc.Group( align="center", justify="center", - gap="md", children=[ dmc.Text("Heating degree day (HDD) setpoint"), dmc.NumberInput( @@ -141,7 +138,7 @@ def update_layout(si_ip): ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER, gap=0), + children=dmc.Stack(id=ElementIds.DEGREE_DAYS_CHART_WRAPPER), ), title_with_link( text="Climate Profiles", @@ -197,7 +194,7 @@ def update_map(meta): def update_location_info(ts, df, meta, si_ip): """Update the contents of tab two. Passing in the general info (df, meta).""" location = f"Location: {meta[ColNames.CITY]}, {meta[ColNames.COUNTRY]}" - lon = f"Longitude: {meta[ColNames.LON]}" + lon = f" Longitude: {meta[ColNames.LON]}" lat = f"Latitude: {meta[ColNames.LAT]}" site_elevation = round(float(meta[ColNames.SITE_ELEVATION]), 2) @@ -249,7 +246,6 @@ def update_location_info(ts, df, meta, si_ip): coldest_yearly_tmp = f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} {tmp_unit}" return dmc.Stack( - gap=4, children=[ dmc.Text(location, fw=700), dmc.Text(lon), @@ -269,114 +265,105 @@ def update_location_info(ts, df, meta, si_ip): @callback( [ Output(ElementIds.DEGREE_DAYS_CHART_WRAPPER, "children"), - Output(ElementIds.WARNING_CDD_HIGHER_HDD, "children"), + Output(ElementIds.WARNING_CDD_HIGHER_HDD, "is-open"), ], [ Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), - Input(ElementIds.SUBMIT_SET_POINTS, "n_clicks_timestamp"), + Input(ElementIds.SUBMIT_SET_POINTS, "n_clicks"), ], [ State(ElementIds.ID_SUMMARY_DF_STORE, "data"), State(ElementIds.ID_SUMMARY_META_STORE, "data"), State(ElementIds.INPUT_HDD_SET_POINT, "value"), State(ElementIds.INPUT_CDD_SET_POINT, "value"), - State(ElementIds.SUBMIT_SET_POINTS, "n_clicks"), State(ElementIds.ID_SUMMARY_SI_IP_UNIT_STORE, "data"), ], + prevent_initial_call=False, ) -def degree_day_chart(ts, ts_click, df, meta, hdd_value, cdd_value, n_clicks, si_ip): - """Update the contents of tab two. Passing in the general info (df, meta).""" +def degree_day_chart(ts, n_clicks, df, meta, hdd_value, cdd_value, si_ip): + """Redraw HDD/CDD chart only when Submit is clicked.""" - ctx = dash.callback_context - - if ( - ctx.triggered[0][ColNames.PROP_ID] == "submit-set-points.n_clicks_timestamp" - or n_clicks is None - ): - hdd_setpoint = hdd_value - cdd_setpoint = cdd_value - - warning_setpoint = cdd_setpoint < hdd_setpoint - - color_hdd = "red" - color_cdd = "dodgerblue" - - hdd_array = [] - cdd_array = [] - months = df[ColNames.MONTH_NAMES].unique() - - for i in range(1, 13): - query_month = "month==" - - # calculates HDD per month - query = query_month + str(i) + " and DBT<=" + str(hdd_setpoint) - a = df.query(query)[ColNames.DBT].sub(hdd_setpoint) - hdd = a.sum(axis=0, skipna=True) / 24 - hdd_array.append(int(hdd)) - - # calculates CDD per month - query = query_month + str(i) + " and DBT>=" + str(cdd_setpoint) - a = df.query(query)[ColNames.DBT].sub(cdd_setpoint) - cdd = a.sum(axis=0, skipna=True) / 24 - cdd_array.append(int(cdd)) - - trace1 = go.Bar( - x=months, - y=hdd_array, - name="Heating Degree Days", - marker_color=color_hdd, - customdata=[abs(ele) for ele in hdd_array], - hovertemplate=( - " Heating Degree Days:
%{customdata} per month
" - ), - ) - trace2 = go.Bar( - x=months, - y=cdd_array, - name="Cooling Degree Days", - marker_color=color_cdd, - customdata=cdd_array, - hovertemplate=( - "Cooling Degree Days:
%{customdata} per month
" - ), - ) + if df is None or meta is None: + raise PreventUpdate - fig = go.Figure(data=[trace2, trace1]) - fig.update_layout( - barmode="relative", - margin=tight_margins, - template=template, - dragmode=False, - legend=dict( - orientation="h", yanchor="bottom", y=1.05, xanchor="right", x=1 - ), - ) + if isinstance(df, (list, tuple, dict)): + df = pd.DataFrame(df) - fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True) - fig.update_yaxes(showline=True, linewidth=1, linecolor="black", mirror=True) + hdd_setpoint = hdd_value + cdd_setpoint = cdd_value + warning_setpoint = cdd_setpoint < hdd_setpoint - custom_inputs = f"{hdd_value}-{cdd_value}" - units = generate_units_degree(si_ip) + color_hdd = "red" + color_cdd = "dodgerblue" - chart = dcc.Graph( - id=ElementIds.DEGREE_DAYS_CHART, - config=generate_chart_name(TabNames.HDD_CDD, meta, custom_inputs, units), - figure=fig, - ) + hdd_array, cdd_array = [], [] + months = df[ColNames.MONTH_NAMES].unique() - alert_children = ( - dmc.Alert( - "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!", - color="yellow", - variant="filled", - title="Warning", - radius="md", - withCloseButton=True, - ) - if warning_setpoint - else None + for i in range(1, 13): + query_month = "month==" + + a = df.query(query_month + str(i) + " and DBT<=" + str(hdd_setpoint))[ + ColNames.DBT + ].sub(hdd_setpoint) + hdd_array.append(int(a.sum(skipna=True) / 24)) + + a = df.query(query_month + str(i) + " and DBT>=" + str(cdd_setpoint))[ + ColNames.DBT + ].sub(cdd_setpoint) + cdd_array.append(int(a.sum(skipna=True) / 24)) + + trace1 = go.Bar( + x=months, + y=hdd_array, + name="Heating Degree Days", + marker_color=color_hdd, + customdata=[abs(x) for x in hdd_array], + hovertemplate=" Heating Degree Days:
%{customdata} per month
", + ) + + trace2 = go.Bar( + x=months, + y=cdd_array, + name="Cooling Degree Days", + marker_color=color_cdd, + customdata=cdd_array, + hovertemplate="Cooling Degree Days:
%{customdata} per month
", + ) + + fig = go.Figure(data=[trace2, trace1]) + fig.update_layout( + barmode="relative", + margin=tight_margins, + template=template, + dragmode=False, + legend=dict(orientation="h", yanchor="bottom", y=1.05, xanchor="right", x=1), + yaxis=dict(range=[-100, 400]), + ) + fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True) + fig.update_yaxes(showline=True, linewidth=1, linecolor="black", mirror=True) + + custom_inputs = f"{hdd_value}-{cdd_value}" + units = generate_units_degree(si_ip) + + chart = dcc.Graph( + id=ElementIds.DEGREE_DAYS_CHART, + config=generate_chart_name(TabNames.HDD_CDD, meta, custom_inputs, units), + figure=fig, + ) + + alert_children = ( + dmc.Alert( + "WARNING: Invalid Results! The CDD setpoint should be higher than the HDD setpoint!", + color="yellow", + variant="filled", + title="Warning", + withCloseButton=True, ) - return chart, alert_children + if warning_setpoint + else None + ) + + return chart, alert_children @callback( diff --git a/pages/sun.py b/pages/sun.py index 315be16d..4c9b2aef 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -57,7 +57,7 @@ def sun_path(): """Return the layout for the custom sun path and its dropdowns.""" return dmc.Stack( - gap="md", + p="sm", children=[ title_with_link( text="Sun path chart", @@ -67,9 +67,8 @@ def sun_path(): dmc.Group( align="center", justify="center", - gap="md", children=[ - dmc.Title("View: ", order=6, w="10rem", fz="xl"), + dmc.Title("View: ", order=5), dropdown( id=ElementIds.CUSTOM_SUN_VIEW_DROPDOWN, options={ @@ -84,9 +83,8 @@ def sun_path(): dmc.Group( align="center", justify="center", - gap="md", children=[ - dmc.Title("Select Variable: ", order=6, w="10rem", fz="xl"), + dmc.Title("Select Variable: ", order=5), dropdown( id=ElementIds.CUSTOM_SUN_VAR_DROPDOWN, options=sc_dropdown_names, @@ -106,7 +104,7 @@ def sun_path(): def explore_daily_heatmap(): """Contents of the bottom part of the tab""" return dmc.Stack( - gap="md", + p="sm", w="100%", children=[ title_with_link( @@ -117,9 +115,8 @@ def explore_daily_heatmap(): dmc.Group( align="center", justify="center", - gap="md", children=[ - dmc.Title("Select variable: ", order=6, w="10rem"), + dmc.Title("Select variable: ", order=5), dropdown( id=ElementIds.TAB_EXPLORE_DROPDOWN, options=sun_cloud_tab_explore_dropdown_names, @@ -142,7 +139,7 @@ def explore_daily_heatmap(): def static_section(): return dmc.Stack( id=ElementIds.STATIC_SECTION, - gap="md", + p="sm", w="100%", children=[ # ... @@ -153,7 +150,7 @@ def static_section(): def layout(): """Contents of tab four.""" return dmc.Stack( - gap="md", + p="sm", w="100%", id=ElementIds.TAB_FOUR_CONTAINER, children=[sun_path(), static_section(), explore_daily_heatmap()], diff --git a/pages/t_rh.py b/pages/t_rh.py index 7fd9707d..7d57f698 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -39,7 +39,6 @@ def layout(): dmc.Group( justify="center", align="center", - gap="sm", wrap="nowrap", children=[ dmc.Text("Select a variable:", fz="xl"), @@ -52,7 +51,6 @@ def layout(): ], ), dmc.Stack( - gap="lg", mt="md", children=[ # Yearly Chart @@ -63,7 +61,7 @@ def layout(): ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.YEARLY_CHART, gap=0), + children=dmc.Stack(id=ElementIds.YEARLY_CHART), ), # Daily chart title_with_link( @@ -73,7 +71,7 @@ def layout(): ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.DAILY, gap=0), + children=dmc.Stack(id=ElementIds.DAILY), ), # Heatmap chart title_with_link( @@ -83,7 +81,7 @@ def layout(): ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.HEATMAP, gap=0), + children=dmc.Stack(id=ElementIds.HEATMAP), ), # Descriptive statistics title_with_tooltip( @@ -91,7 +89,7 @@ def layout(): tooltip_text="count, mean, std, min, max, and percentiles", id_button=IdButtons.TABLE_TMP_RH, ), - dmc.Stack(id=ElementIds.TABLE_TMP_HUM, gap=0), + dmc.Stack(id=ElementIds.TABLE_TMP_HUM), ], ), ], diff --git a/pages/wind.py b/pages/wind.py index 38204b9e..e3d5be43 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -32,12 +32,8 @@ def sliders(): """Returns 2 sliders for the hour""" return dmc.Stack( id=ElementIds.SLIDER_CONTAINER, - gap="md", - align="center", children=[ dmc.Group( - gap="sm", - align="center", children=[ dmc.Text("Month Range"), dcc.RangeSlider( @@ -53,8 +49,6 @@ def sliders(): ], ), dmc.Group( - gap="sm", - align="center", children=[ dmc.Text("Hour Range"), dcc.RangeSlider( @@ -76,7 +70,6 @@ def sliders(): def seasonal_wind_rose(): """Return the section with the 4 seasonal wind rose graphs.""" return dmc.Stack( - gap="md", children=[ title_with_link( text="Seasonal Wind Rose", @@ -89,12 +82,11 @@ def seasonal_wind_rose(): dmc.GridCol( span=6, children=dmc.Stack( - gap="xs", children=[ dcc.Loading( type="circle", children=dmc.Stack( - id=ElementIds.WINTER_WIND_ROSE, w="100%" + id=ElementIds.WINTER_WIND_ROSE, ), ), dmc.Text(id=ElementIds.WINTER_WIND_ROSE_TEXT), @@ -104,12 +96,11 @@ def seasonal_wind_rose(): dmc.GridCol( span=6, children=dmc.Stack( - gap="xs", children=[ dcc.Loading( type="circle", children=dmc.Stack( - id=ElementIds.SPRING_WIND_ROSE, w="100%" + id=ElementIds.SPRING_WIND_ROSE, ), ), dmc.Text(id=ElementIds.SPRING_WIND_ROSE_TEXT), @@ -119,12 +110,11 @@ def seasonal_wind_rose(): dmc.GridCol( span=6, children=dmc.Stack( - gap="xs", children=[ dcc.Loading( type="circle", children=dmc.Stack( - id=ElementIds.SUMMER_WIND_ROSE, w="100%" + id=ElementIds.SUMMER_WIND_ROSE, ), ), dmc.Text(id=ElementIds.SUMMER_WIND_ROSE_TEXT), @@ -134,12 +124,11 @@ def seasonal_wind_rose(): dmc.GridCol( span=6, children=dmc.Stack( - gap="xs", children=[ dcc.Loading( type="circle", children=dmc.Stack( - id=ElementIds.FALL_WIND_ROSE, w="100%" + id=ElementIds.FALL_WIND_ROSE, ), ), dmc.Text(id=ElementIds.FALL_WIND_ROSE_TEXT), @@ -155,7 +144,6 @@ def seasonal_wind_rose(): def daily_wind_rose(): """Return the section for the 3 daily wind rose graphs.""" return dmc.Stack( - gap="md", id=ElementIds.TAB5_DAILY_CONTAINER, children=[ title_with_link( @@ -164,17 +152,15 @@ def daily_wind_rose(): doc_link=DocLinks.WIND_ROSE, ), dmc.Grid( - gutter="md", children=[ dmc.GridCol( span=4, children=dmc.Stack( - gap="xs", children=[ dcc.Loading( type="circle", children=dmc.Stack( - id=ElementIds.MORNING_WIND_ROSE, w="100%" + id=ElementIds.MORNING_WIND_ROSE, ), ), dmc.Text(id=ElementIds.MORNING_WIND_ROSE_TEXT), @@ -184,12 +170,11 @@ def daily_wind_rose(): dmc.GridCol( span=4, children=dmc.Stack( - gap="xs", children=[ dcc.Loading( type="circle", children=dmc.Stack( - id=ElementIds.NOON_WIND_ROSE, w="100%" + id=ElementIds.NOON_WIND_ROSE, ), ), dmc.Text(id=ElementIds.NOON_WIND_ROSE_TEXT), @@ -199,12 +184,11 @@ def daily_wind_rose(): dmc.GridCol( span=4, children=dmc.Stack( - gap="xs", children=[ dcc.Loading( type="circle", children=dmc.Stack( - id=ElementIds.NIGHT_WIND_ROSE, w="100%" + id=ElementIds.NIGHT_WIND_ROSE, ), ), dmc.Text(id=ElementIds.NIGHT_WIND_ROSE_TEXT), @@ -219,32 +203,24 @@ def daily_wind_rose(): def custom_wind_rose(): return dmc.Stack( - gap="md", - align="stretch", # stretch 让子项默认靠左 children=[ - # 标题靠左 title_with_tooltip( text="Customizable Wind Rose", tooltip_text=None, id_button=IdButtons.CUSTOM_ROSE_CHART, ), - # 参数区(保持居中排布) dmc.Grid( gutter="md", justify="center", align="center", maw=900, mx="auto", - w="100%", children=[ dmc.GridCol( span=6, children=dmc.Stack( - gap="md", - align="center", children=[ dmc.Group( - gap="md", children=[ dmc.Title( "Start Month:", @@ -264,7 +240,6 @@ def custom_wind_rose(): ], ), dmc.Group( - gap="md", children=[ dmc.Title( "Start Hour:", order=6, w="8rem", ta="right" @@ -285,11 +260,8 @@ def custom_wind_rose(): dmc.GridCol( span=6, children=dmc.Stack( - gap="md", - align="center", children=[ dmc.Group( - gap="md", children=[ dmc.Title( "End Month:", order=6, w="8rem", ta="right" @@ -306,7 +278,6 @@ def custom_wind_rose(): ], ), dmc.Group( - gap="md", children=[ dmc.Title( "End Hour:", order=6, w="8rem", ta="right" @@ -328,9 +299,7 @@ def custom_wind_rose(): ), dcc.Loading( type="circle", - children=dmc.Stack( - id=ElementIds.CUSTOM_WIND_ROSE, w="100%", maw=900, mx="auto" - ), + children=dmc.Stack(id=ElementIds.CUSTOM_WIND_ROSE, maw=900, mx="auto"), ), ], ) @@ -339,8 +308,6 @@ def custom_wind_rose(): def layout(): """Contents in the fifth tab 'Wind'.""" return dmc.Stack( - gap="md", - align="stretch", children=[ title_with_link( text="Annual Wind Rose", @@ -349,15 +316,15 @@ def layout(): ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.WIND_ROSE, w="100%"), + children=dmc.Stack(id=ElementIds.WIND_ROSE), ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.WIND_SPEED, w="100%"), + children=dmc.Stack(id=ElementIds.WIND_SPEED), ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.WIND_DIRECTION, w="100%"), + children=dmc.Stack(id=ElementIds.WIND_DIRECTION), ), seasonal_wind_rose(), daily_wind_rose(), diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index 26013789..2adfcbdc 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -19,8 +19,7 @@ function click_tab(name) { cy.get('#nav-group-main').click({ force: true }); // Locate tab item by ID prefix, then find label by text cy.get('[id^="nav-"]', { timeout: 10000 }).contains(name).click({ force: true }); - // Wait for tab content container to appear - cy.get('#tabs-content', { timeout: 20000 }).should('exist'); + cy.wait(500); } function load_epw() { @@ -32,7 +31,7 @@ describe('Clima', () => { cy.visit('http://127.0.0.1:8080'); cy.contains('CBE Clima Tool'); cy.contains('Current Location: N/A'); - + // Upload load_epw() cy.contains('The EPW was successfully loaded!'); @@ -124,8 +123,6 @@ describe('Clima', () => { cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true }); cy.contains('Global Value Ranges', { timeout: 10000 }).click({ force: true }); cy.contains('-40'); // Global minimum: not something you see in Italy! - // Reopen sidebar - cy.get('#burger-button', { timeout: 10000 }).click(); cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true }); cy.contains('IP').click({ force: true }); cy.contains('100'); // Not a Celsius temperature! From 5ca3b07712d8adbd4fe87fe1c89f2ec4232a458d Mon Sep 17 00:00:00 2001 From: yluo0664 Date: Wed, 17 Sep 2025 19:05:47 +1000 Subject: [PATCH 089/163] fix: removed the styles in the function show_alert_after_delay while using default style --- pages/lib/layout.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/pages/lib/layout.py b/pages/lib/layout.py index d6891777..650a0ba9 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -198,10 +198,13 @@ def create_header(): color="blue", variant="filled", withCloseButton=True, - style={"display": "none"}, + w=400, + pos="fixed", + top="25px", + right="10px", + style={"zIndex": 1002, "display": "none"}, ), ], - h="100%", px="md", ) @@ -412,15 +415,4 @@ def update_nav_active_state(pathname): prevent_initial_call=True, ) def show_alert_after_delay(n_intervals): - base_style = { - "position": "fixed", - "top": "25px", - "right": "10px", - "width": "400px", - "zIndex": 1002, - } - - if n_intervals == 1: - return {**base_style, "display": "block"} - else: - return {**base_style, "display": "none"} + return {"display": "block" if n_intervals == 1 else "none"} From b6009c1329b09ca1056fd4fa4b7c2c84ef9ea38d Mon Sep 17 00:00:00 2001 From: yluo0664 Date: Tue, 23 Sep 2025 18:28:37 +1000 Subject: [PATCH 090/163] fix: removed the unnecessary style and optimized spacing --- pages/explorer.py | 423 +++++++++++++----------------- pages/lib/layout.py | 106 ++++---- pages/lib/utils.py | 10 +- pages/natural_ventilation.py | 131 ++++----- pages/outdoor.py | 83 +++--- pages/psy-chart.py | 240 ++++++++--------- pages/select.py | 11 +- pages/summary.py | 5 +- pages/sun.py | 16 +- pages/t_rh.py | 4 +- pages/wind.py | 13 +- tests/node/cypress/e2e/spec.cy.js | 3 +- 12 files changed, 462 insertions(+), 583 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index bc36fc65..c6162dc9 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -81,18 +81,13 @@ def section_one(): return dmc.Stack( children=[ section_one_inputs(), - # Yearly chart title_with_link( text="Yearly chart", id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL, doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), dcc.Loading( - type="circle", - children=dmc.Paper( - id=ElementIds.YEARLY_EXPLORE, - p="sm", - ), + type="circle", children=dmc.Paper(id=ElementIds.YEARLY_EXPLORE, p="sm") ), title_with_link( text="Daily chart", @@ -100,11 +95,7 @@ def section_one(): doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), dcc.Loading( - type="circle", - children=dmc.Paper( - id=ElementIds.QUERY_DAILY, - p="sm", - ), + type="circle", children=dmc.Paper(id=ElementIds.QUERY_DAILY, p="sm") ), title_with_link( text="Heatmap chart", @@ -112,35 +103,26 @@ def section_one(): doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, ), dcc.Loading( - type="circle", - children=dmc.Paper( - id=ElementIds.QUERY_HEATMAP, - p="sm", - ), + type="circle", children=dmc.Paper(id=ElementIds.QUERY_HEATMAP, p="sm") ), title_with_tooltip( text="Descriptive statistics", tooltip_text="count, mean, std, min, max, and percentiles", id_button=IdButtons.TABLE_EXPLORE, ), - dmc.SimpleGrid( - cols={"base": 1, "md": 3}, - children=[ - dmc.Stack(), - dmc.Stack( + dmc.Center( + children=dmc.Box( + w="33%", + children=dmc.Stack( children=[ dmc.Button( "Apply month and hour filter", id=ElementIds.SEC1_TIME_FILTER_INPUT, - variant="filled", color="blue", - size="md", - fullWidth=True, ), - # Month - dmc.Flex( + dmc.Group( children=[ - dmc.Text("Month Range", miw=110), + dmc.Title("Month Range", order=5), dmc.Stack( flex=1, children=dcc.RangeSlider( @@ -166,9 +148,9 @@ def section_one(): ), ], ), - dmc.Flex( + dmc.Group( children=[ - dmc.Text("Hour Range", miw=110), + dmc.Title("Hour Range", order=5), dmc.Stack( flex=1, children=dcc.RangeSlider( @@ -196,9 +178,9 @@ def section_one(): ), ], ), - dmc.Stack(), - ], + ) ), + # Results table dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"), ], ) @@ -207,6 +189,7 @@ def section_one(): def section_two_inputs(): """Return all the input forms from section two.""" return dmc.Stack( + p="md", children=[ title_with_tooltip( text="Customizable heatmap", @@ -214,41 +197,35 @@ def section_two_inputs(): id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL, ), dmc.SimpleGrid( - cols={"base": 1, "md": 3}, + cols=3, + spacing="md", children=[ - dmc.Stack( - children=[ - dmc.Flex( - children=[ - dmc.Text("Variable:", miw=110, ml="md"), - dmc.Stack( - flex=1, - children=dropdown( - id=ElementIds.SEC2_VAR_DROPDOWN, - options=explore_dropdown_names, - value=ColNames.RH, - ), - ), - ], + dmc.Group( + [ + dmc.Title("Variable:", order=5), + dmc.Stack( + dropdown( + id=ElementIds.SEC2_VAR_DROPDOWN, + options=explore_dropdown_names, + value=ColNames.RH, + ), + flex=1, ), ], + align="flex-start", ), dmc.Stack( - children=[ + [ dmc.Button( "Apply month and hour filter", id=ElementIds.SEC2_TIME_FILTER_INPUT, - variant="filled", color="blue", - size="md", - fullWidth=True, ), - dmc.Flex( - children=[ - dmc.Text("Month Range", miw=110), + dmc.Group( + [ + dmc.Title("Month Range", order=5), dmc.Stack( - flex=1, - children=dcc.RangeSlider( + dcc.RangeSlider( id=ElementIds.SEC2_MONTH_SLIDER, min=1, max=12, @@ -257,10 +234,10 @@ def section_two_inputs(): marks={1: "1", 12: "12"}, tooltip={ "always_visible": False, - "placement": "top", + "placement": "topLeft", }, - allowCross=False, ), + flex=1, ), dcc.Checklist( id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, @@ -271,12 +248,11 @@ def section_two_inputs(): ), ], ), - dmc.Flex( - children=[ - dmc.Text("Hour Range", miw=110), + dmc.Group( + [ + dmc.Title("Hour Range", order=5), dmc.Stack( - flex=1, - children=dcc.RangeSlider( + dcc.RangeSlider( id=ElementIds.SEC2_HOUR_SLIDER, min=0, max=24, @@ -287,8 +263,8 @@ def section_two_inputs(): "always_visible": False, "placement": "topLeft", }, - allowCross=False, ), + flex=1, ), dcc.Checklist( id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, @@ -302,53 +278,48 @@ def section_two_inputs(): ], ), dmc.Stack( - children=[ + [ dmc.Button( "Apply filter", id=ElementIds.SEC2_DATA_FILTER_INPUT, - variant="filled", color="blue", - size="md", - fullWidth=True, ), - dmc.Flex( - children=[ - dmc.Text("Filter Variable:", miw=130), + dmc.Group( + [ + dmc.Title("Filter Variable:", order=5), dmc.Stack( - flex=1, - children=dropdown( + dropdown( id=ElementIds.SEC2_DATA_FILTER_VAR, options=explore_dropdown_names, value=ColNames.RH, ), + flex=1, ), ], ), - dmc.Flex( - children=[ - dmc.Text("Min Value:", miw=130), + dmc.Group( + [ + dmc.Title("Min Value:", order=5), dmc.Stack( - flex=1, - children=dmc.NumberInput( + dmc.NumberInput( id=ElementIds.SEC2_MIN_VAL, placeholder="Enter a number for the min val", value=0, - step=1, ), + flex=1, ), ], ), - dmc.Flex( - children=[ - dmc.Text("Max Value:", miw=130), + dmc.Group( + [ + dmc.Title("Max Value:", order=5), dmc.Stack( - flex=1, - children=dmc.NumberInput( + dmc.NumberInput( id=ElementIds.SEC2_MAX_VAL, placeholder="Enter a number for the max val", value=100, - step=1, ), + flex=1, ), ], ), @@ -387,7 +358,6 @@ def section_two(): dcc.Loading( type="circle", children=dmc.Paper( - p="sm", children=dcc.Graph( id=ElementIds.CUSTOM_SUMMARY, config=fig_config, @@ -399,173 +369,148 @@ def section_two(): def section_three_inputs(): - """""" - return dmc.Stack( + return dmc.SimpleGrid( + cols=3, children=[ - dmc.SimpleGrid( - cols={"base": 1, "md": 3}, - children=[ - dmc.Stack( - children=[ - dmc.Flex( - children=[ - dmc.Text("X Variable:", miw=110, ml="md"), - dmc.Stack( - flex=1, - children=dropdown( - id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, - options=explore_dropdown_names, - value="DBT", - ), - ), - ], + dmc.Stack( + [ + dmc.Group( + [ + dmc.Title("X Variable:", order=5), + dmc.Stack( + dropdown( + id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, + options=explore_dropdown_names, + value="DBT", + ), + flex=1, ), - dmc.Flex( - children=[ - dmc.Text("Y Variable:", miw=110, ml="md"), - dmc.Stack( - flex=1, - children=dropdown( - id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, - options=explore_dropdown_names, - value=ColNames.RH, - ), - ), - ], - ), - dmc.Flex( - children=[ - dmc.Text("Color By:", miw=110, ml="md"), - dmc.Stack( - flex=1, - children=dropdown( - id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, - options=explore_dropdown_names, - value="glob_hor_rad", - ), - ), - ], + ], + ), + dmc.Group( + [ + dmc.Title("Y Variable:", order=5), + dmc.Stack( + dropdown( + id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, + options=explore_dropdown_names, + value=ColNames.RH, + ), + flex=1, ), ], ), - dmc.Stack( - children=[ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, - variant="filled", - color="blue", - size="md", - fullWidth=True, + dmc.Group( + [ + dmc.Title("Color By:", order=5), + dmc.Stack( + dropdown( + id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, + options=explore_dropdown_names, + value="glob_hor_rad", + ), + flex=1, ), - dmc.Flex( - children=[ - dmc.Text("Month Range", miw=110), - dmc.Stack( - flex=1, - children=dcc.RangeSlider( - id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - ), - dcc.Checklist( - id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], - ), - ], + ], + ), + ], + ), + dmc.Stack( + [ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, + color="blue", + ), + dmc.Group( + [ + dmc.Title("Month Range", order=5), + dmc.Stack( + dcc.RangeSlider( + id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, + min=1, + max=12, + value=[1, 12], + marks={1: "1", 12: "12"}, + ), + flex=1, ), - dmc.Flex( - children=[ - dmc.Text("Hour Range", miw=110), - dmc.Stack( - flex=1, - children=dcc.RangeSlider( - id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - ), - dcc.Checklist( - id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], - ), - ], + dcc.Checklist( + id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, + options=[{"label": "Invert", "value": "invert"}], + value=[], ), ], ), - dmc.Stack( - children=[ - dmc.Button( - "Apply filter", - id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, - variant="filled", - color="blue", - size="md", - fullWidth=True, + dmc.Group( + [ + dmc.Title("Hour Range", order=5), + dmc.Stack( + dcc.RangeSlider( + id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, + min=0, + max=24, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + ), + flex=1, ), - dmc.Flex( - children=[ - dmc.Text("Filter Variable:", miw=130), - dmc.Stack( - flex=1, - children=dropdown( - id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, - options=explore_dropdown_names, - value=ColNames.RH, - ), - ), - ], + dcc.Checklist( + id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, + options=[{"label": "Invert", "value": "invert"}], + value=[], ), - dmc.Flex( - children=[ - dmc.Text("Min Value:", miw=130), - dmc.Stack( - flex=1, - children=dmc.NumberInput( - id=ElementIds.TAB6_SEC3_MIN_VAL, - placeholder="Enter a number for the min val", - value=0, - step=1, - ), - ), - ], + ], + ), + ], + ), + dmc.Stack( + [ + dmc.Button( + "Apply filter", + id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, + color="blue", + ), + dmc.Group( + [ + dmc.Title("Filter Variable:", order=5), + dmc.Stack( + dropdown( + id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, + options=explore_dropdown_names, + value=ColNames.RH, + ), + flex=1, ), - dmc.Flex( - children=[ - dmc.Text("Max Value:", miw=130), - dmc.Stack( - flex=1, - children=dmc.NumberInput( - id=ElementIds.TAB6_SEC3_MAX_VAL, - placeholder="Enter a number for the max val", - value=100, - step=1, - ), - ), - ], + ], + ), + dmc.Group( + [ + dmc.Title("Min Value:", order=5), + dmc.Stack( + dmc.NumberInput( + id=ElementIds.TAB6_SEC3_MIN_VAL, + placeholder="Enter a number for the min val", + value=0, + ), + flex=1, + ), + ], + ), + dmc.Group( + [ + dmc.Title("Max Value:", order=5), + dmc.Stack( + dmc.NumberInput( + id=ElementIds.TAB6_SEC3_MAX_VAL, + placeholder="Enter a number for the max val", + value=100, + ), + flex=1, ), ], ), diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 650a0ba9..2b532fb3 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -41,7 +41,7 @@ def get_icon(cls, page_name): def create_navbar(): nav_link_styles = { "root": { - "borderRadius": "6px", + "borderRadius": "0.375rem", "transition": "all 0.2s ease", "&:hover": {"backgroundColor": "#e3f2fd"}, "&[data-active='true']": { @@ -66,7 +66,6 @@ def create_navbar(): href=page[ColNames.PATH], id=f"nav-{page[ColNames.PATH].replace('/', '')}", active=False, - mb="2px", styles=nav_link_styles, ) for page in dash.page_registry.values() @@ -78,10 +77,11 @@ def create_navbar(): children=sub_links, id=ElementIds.NAV_GROUP_MAIN, variant="light", + childrenOffset=0, + opened=True, ) segmented_control_styles = { - "root": {"width": "100%"}, "control": {"flex": 1, "minWidth": 0}, } @@ -89,29 +89,51 @@ def create_navbar(): gap="xs", py="xs", children=[ - dmc.SegmentedControl( - id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT, - value="local", - color="blue", - data=[ - {"label": "Global Value Ranges", "value": "global"}, - {"label": "Local Value Ranges", "value": "local"}, - ], - radius="md", - size="sm", - styles=segmented_control_styles, + dmc.Tooltip( + label=dmc.Stack( + gap="xs", + children=[ + dmc.Text( + "You can choose value ranges between Global and Local" + ), + ], + ), + position="right", + withArrow=True, + children=dmc.SegmentedControl( + id=ElementIds.ID_LAYOUT_GLOBAL_LOCAL_RADIO_INPUT, + value="local", + color="blue", + data=[ + {"label": "Global", "value": "global"}, + {"label": "Local", "value": "local"}, + ], + w=220, + size="sm", + styles=segmented_control_styles, + ), ), - dmc.SegmentedControl( - id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT, - value=UnitSystem.SI, - color="blue", - data=[ - {"label": UnitSystem.SI.upper(), "value": UnitSystem.SI}, - {"label": UnitSystem.IP.upper(), "value": UnitSystem.IP}, - ], - radius="md", - size="sm", - styles=segmented_control_styles, + dmc.Tooltip( + label=dmc.Stack( + gap="xs", + children=[ + dmc.Text("You can choose units between SI and IP"), + ], + ), + position="right", + withArrow=True, + children=dmc.SegmentedControl( + id=ElementIds.ID_LAYOUT_SI_IP_RADIO_INPUT, + value=UnitSystem.SI, + color="blue", + data=[ + {"label": "SI", "value": UnitSystem.SI}, + {"label": "IP", "value": UnitSystem.IP}, + ], + w=220, + size="sm", + styles=segmented_control_styles, + ), ), ], ) @@ -122,7 +144,7 @@ def create_navbar(): children=[controls_stack], id=ElementIds.NAV_GROUP_CONTROLS, variant="light", - childrenOffset=8, + childrenOffset=0, ) # Documentation @@ -136,7 +158,6 @@ def create_navbar(): return dmc.ScrollArea( children=dmc.Stack(gap="xs", children=[parent_group, controls_group, doc_link]), - pr="xs", ) @@ -160,8 +181,6 @@ def create_header(): "CBE Clima Tool", id=ElementIds.BANNER_TITLE, order=2, - fw=500, - ff="'Open Sans', sans-serif", lh=1.1, c="white", ), @@ -170,13 +189,11 @@ def create_header(): id=ElementIds.ID_LAYOUT_BANNER_SUBTITLE, size="sm", opacity=0.85, - ff="'Poppins', sans-serif", - fw=400, - h=25, style={"overflow": "hidden"}, c="white", ), ], + p="xs", ), dmc.Alert( [ @@ -186,7 +203,7 @@ def create_header(): href="https://forms.gle/k289zP3R92jdu14M7", target="_blank", c="white", - underline=True, + underline="always", ), "! ☀️", ], @@ -200,21 +217,19 @@ def create_header(): withCloseButton=True, w=400, pos="fixed", - top="25px", - right="10px", + top="1em", + right="1em", style={"zIndex": 1002, "display": "none"}, ), ], - px="md", + pl="md", ) def create_footer(): white_anchor_style = { - "underline": True, + "underline": "always", "c": "white", - "fz": "md", - "fw": 500, "target": "_blank", } @@ -259,7 +274,7 @@ def create_footer(): Please cite us: Betti, G., Tartarini, F., Nguyen, C, Schiavon, S. CBE Clima Tool: A free and open-source web application for climate analysis tailored to sustainable building design. Build. Simul. (2023). [https://doi.org/10.1007/s12273-023-1090-5](https://doi.org/10.1007/s12273-023-1090-5). """, style={ - "fontSize": "16px", + "fontSize": "1rem", "lineHeight": 1.3, "fontWeight": 400, "color": "white", @@ -285,7 +300,6 @@ def create_footer(): p="sm", c="white", bg="#003262", - h="100%", gap="xl", justify="flex-start", align="center", @@ -324,7 +338,6 @@ def create_collapsible_layout(): dmc.AppShellNavbar( id=ElementIds.NAVBAR, children=create_navbar(), - p="md", bg="#f8f9fa", ), # including main and footer @@ -339,20 +352,19 @@ def create_collapsible_layout(): pos="relative", style={ "zIndex": 1, - "@media (max-width: 768px)": { - "left": "0px", + "@media (max-width: 48rem)": { + "left": "0", }, }, ), ], header={"height": 80}, navbar={ - "width": 180, + "width": 230, "breakpoint": "sm", "collapsed": {"mobile": True, "desktop": False}, "id": ElementIds.NAVBAR_CONTAINER, }, - padding="md", id=ElementIds.APP_SHELL, ) @@ -377,7 +389,7 @@ def toggle_navbar_and_width( ): navbar["collapsed"] = {"mobile": not burger_opened, "desktop": not burger_opened} - WIDTHS = {"default": 180, "pages": 300, "tools": 400} + WIDTHS = {"default": 230, "pages": 230, "tools": 230} if tools_opened is not None: tools_expanded = tools_opened diff --git a/pages/lib/utils.py b/pages/lib/utils.py index 53d9c26f..ff44face 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -149,7 +149,6 @@ def generate_custom_inputs_psy( def title_with_tooltip(text, tooltip_text, id_button): if tooltip_text: return dmc.Group( - align="center", mt="md", px="md", children=[ @@ -171,7 +170,13 @@ def title_with_tooltip(text, tooltip_text, id_button): ], ) else: - return dmc.Title(text, order=3, mt="md", px="md") + return dmc.Group( + mt="md", + px="md", + children=[ + dmc.Title(text, order=3), + ], + ) def title_with_link( @@ -181,7 +186,6 @@ def title_with_link( doc_link: str = "", ): return dmc.Group( - align="center", mt="md", px="md", children=[ diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 4499cc8d..4cd2cc94 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -39,7 +39,7 @@ def layout(): - return dmc.Stack(id=ElementIds.MAIN_NV_SECTION) + return dmc.Stack(p="md", id=ElementIds.MAIN_NV_SECTION) @callback( @@ -67,7 +67,6 @@ def update_layout(si_ip): type="circle", children=dmc.Paper( id=ElementIds.NV_HEATMAP_CHART, - p="sm", ), ), dmc.Group( @@ -77,7 +76,6 @@ def update_layout(si_ip): id=ElementIds.SWITCHES_INPUT, label="", checked=True, - size="md", color="blue", style={"padding": "1rem", "marginRight": "-2rem"}, ), @@ -95,7 +93,6 @@ def update_layout(si_ip): type="circle", children=dmc.Paper( id=ElementIds.NV_BAR_CHART, - p="sm", ), ), ] @@ -103,77 +100,70 @@ def update_layout(si_ip): def inputs_tab(t_min, t_max, d_set): return dmc.SimpleGrid( - cols={"base": 1, "md": 3}, + cols=3, + spacing="md", children=[ dmc.Stack( - children=[ + [ dmc.Button( "Apply filter", - color="primary", + color="blue", id=ElementIds.NV_DBT_FILTER, variant="link", - size="md", - fullWidth=True, n_clicks=1, ), - dmc.Text( - "Outdoor dry-bulb air temperature range", - size="md", - ml="md", - ), - dmc.Flex( - children=[ - dmc.Text("Min Value:", size="md", ml="md"), - dmc.NumberInput( - id=ElementIds.NV_TDB_MIN_VAL, - placeholder="Enter a number for the min val", - step=1, - value=t_min, + dmc.Title("Outdoor dry-bulb air temperature range", order=5), + dmc.Group( + [ + dmc.Title("Min Value:", order=5), + dmc.Stack( + dmc.NumberInput( + id=ElementIds.NV_TDB_MIN_VAL, + placeholder="Enter a number for the min val", + step=1, + value=t_min, + ), + flex=1, ), ], ), - # Max - dmc.Flex( - children=[ - dmc.Text("Max Value:", size="md", ml="md"), - dmc.NumberInput( - id=ElementIds.NV_TDB_MAX_VAL, - placeholder="Enter a number for the max val", - value=t_max, - step=1, + dmc.Group( + [ + dmc.Title("Max Value:", order=5), + dmc.Stack( + dmc.NumberInput( + id=ElementIds.NV_TDB_MAX_VAL, + placeholder="Enter a number for the max val", + value=t_max, + step=1, + ), + flex=1, ), ], ), - ], + ] ), dmc.Stack( - children=[ + [ dmc.Button( "Apply month and hour filter", - color="primary", + color="blue", id=ElementIds.NV_MONTH_HOUR_FILTER, variant="link", - size="md", - fullWidth=True, ), - dmc.Flex( - children=[ - dmc.Text("Month Range", size="md", miw=120), + dmc.Group( + [ + dmc.Title("Month Range", order=5), dmc.Stack( - flex=1, - children=dcc.RangeSlider( + dcc.RangeSlider( id=ElementIds.NV_MONTH_SLIDER, min=1, max=12, step=1, value=[1, 12], marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, ), + flex=1, ), dcc.Checklist( options=[{"label": "Invert", "value": "invert"}], @@ -182,24 +172,19 @@ def inputs_tab(t_min, t_max, d_set): ), ], ), - dmc.Flex( - children=[ - dmc.Text("Hour Range", size="md", miw=120), + dmc.Group( + [ + dmc.Title("Hour Range", order=5), dmc.Stack( - flex=1, - children=dcc.RangeSlider( + dcc.RangeSlider( id=ElementIds.NV_HOUR_SLIDER, min=0, max=24, step=1, value=[0, 24], marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, ), + flex=1, ), dcc.Checklist( options=[{"label": "Invert", "value": "invert"}], @@ -208,19 +193,15 @@ def inputs_tab(t_min, t_max, d_set): ), ], ), - ], + ] ), dmc.Stack( - children=[ + [ dmc.Button( "Apply filter", - color="primary", + color="blue", id=ElementIds.NV_DPT_FILTER, - mb="xs", variant="link", - size="md", - fullWidth=True, - n_clicks=0, disabled=True, ), dcc.Checklist( @@ -232,24 +213,26 @@ def inputs_tab(t_min, t_max, d_set): "system surface temperature, the data point is not plot." ), "value": 1, - }, + } ], value=[], id=ElementIds.ENABLE_CONDENSATION, ), - dmc.Flex( - children=[ - dmc.Text("Surface temperature:", size="md"), - dmc.NumberInput( - id=ElementIds.NV_DPT_MAX_VAL, - placeholder="Enter a number for the max val", - value=d_set, - step=1, - w="50%", + dmc.Group( + [ + dmc.Title("Surface temperature:", order=5), + dmc.Stack( + dmc.NumberInput( + id=ElementIds.NV_DPT_MAX_VAL, + placeholder="Enter a number for the max val", + value=d_set, + step=1, + ), + flex=1, ), ], ), - ], + ] ), ], ) diff --git a/pages/outdoor.py b/pages/outdoor.py index fcc814f4..a7d9ba4d 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -37,58 +37,47 @@ def inputs_outdoor_comfort(): return dmc.SimpleGrid( - cols={"base": 1, "md": 2}, + cols=2, children=[ - dmc.Stack( - children=[ - dmc.Flex( - children=[ - dmc.Text("Select a scenario:", miw=140, ml="md"), - dmc.Stack( - flex=1, - children=dropdown( - id=ElementIds.TAB7_DROPDOWN, - options=outdoor_dropdown_names, - value="utci_Sun_Wind", - persistence=True, - persistence_type="session", - ), - ), - dmc.Center( - children=dmc.Paper(id=ElementIds.IMAGE_SELECTION) - ), - ], + dmc.Group( + [ + dmc.Title("Select a scenario:", order=5), + dmc.Stack( + dropdown( + id=ElementIds.TAB7_DROPDOWN, + options=outdoor_dropdown_names, + value="utci_Sun_Wind", + persistence=True, + persistence_type="session", + ), + flex=1, ), + dmc.Paper(id=ElementIds.IMAGE_SELECTION), ], + align="flex-start", ), dmc.Stack( - children=[ + [ dmc.Button( "Apply month and hour filter", id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, variant="filled", color="blue", - size="md", - fullWidth=True, ), - dmc.Flex( - children=[ - dmc.Text("Month Range", miw=120), + dmc.Group( + [ + dmc.Title("Month Range", order=5), dmc.Stack( - flex=1, - children=dcc.RangeSlider( + dcc.RangeSlider( id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, min=1, max=12, step=1, value=[1, 12], marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, + tooltip={"always_visible": False}, ), + flex=1, ), dcc.Checklist( id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, @@ -97,24 +86,20 @@ def inputs_outdoor_comfort(): ), ], ), - dmc.Flex( - children=[ - dmc.Text("Hour Range", miw=120), + dmc.Group( + [ + dmc.Title("Hour Range", order=5), dmc.Stack( - flex=1, - children=dcc.RangeSlider( + dcc.RangeSlider( id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, min=0, max=24, step=1, value=[0, 24], marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, + tooltip={"always_visible": False}, ), + flex=1, ), dcc.Checklist( id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, @@ -134,9 +119,7 @@ def outdoor_comfort_chart(): children=[ dmc.Paper( id=ElementIds.OUTDOOR_COMFORT_OUTPUT, - p="sm", ), - # UTCI heatmap chart title_with_link( text="UTCI heatmap chart", id_button=IdButtons.UTCI_CHARTS_LABEL, @@ -146,10 +129,8 @@ def outdoor_comfort_chart(): type="circle", children=dmc.Paper( id=ElementIds.UTCI_HEATMAP, - p="sm", ), ), - # UTCI thermal stress chart title_with_link( text="UTCI thermal stress chart", id_button=IdButtons.UTCI_CHARTS_LABEL, @@ -159,17 +140,14 @@ def outdoor_comfort_chart(): type="circle", children=dmc.Paper( id=ElementIds.UTCI_CATEGORY_HEATMAP, - p="sm", ), ), - # Normalize data dmc.Group( justify="center", children=[ dmc.Switch( id=ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, checked=True, - size="md", color="blue", ), title_with_tooltip( @@ -182,12 +160,11 @@ def outdoor_comfort_chart(): ), ], ), - # Summary chart dcc.Loading( type="circle", children=dmc.Paper( id=ElementIds.UTCI_SUMMARY_CHART, - p="sm", + # p="sm", ), ), ], @@ -196,7 +173,7 @@ def outdoor_comfort_chart(): def layout(): return dmc.Stack( - mt="md", + p="md", children=[ dcc.Loading( type="circle", diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 1793e076..4aca01e1 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -57,150 +57,123 @@ def inputs(): - """""" - return dmc.Stack( + return dmc.SimpleGrid( + cols=3, children=[ - dmc.SimpleGrid( - cols={"base": 1, "md": 3}, - spacing="md", - children=[ - dmc.Flex( - align="center", - mt="md", - children=[ - dmc.Text("Color By:", miw=110), + dmc.Group( + [ + dmc.Title("Color By:", order=5), + dmc.Stack( + dropdown( + id=ElementIds.PSY_COLOR_BY_DROPDOWN, + options=psy_dropdown_names, + value="Frequency", + persistence=True, + persistence_type="session", + ), + flex=1, + ), + ], + align="flex-start", + ), + dmc.Stack( + [ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.MONTH_HOUR_FILTER, + color="blue", + ), + dmc.Group( + [ + dmc.Title("Month Range", order=5), dmc.Stack( - flex=1, - children=dropdown( - id=ElementIds.PSY_COLOR_BY_DROPDOWN, - options=psy_dropdown_names, - value="Frequency", - persistence=True, - persistence_type="session", + dcc.RangeSlider( + id=ElementIds.PSY_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={"always_visible": False}, ), + flex=1, + ), + dcc.Checklist( + id=ElementIds.INVERT_MONTH_PSY, + options=[{"label": "Invert", "value": "invert"}], + value=[], ), ], ), - dmc.Stack( - children=[ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.MONTH_HOUR_FILTER, - variant="filled", - color="blue", - size="md", - fullWidth=True, - ), - dmc.Flex( - children=[ - dmc.Text("Month Range", miw=110), - dmc.Stack( - flex=1, - children=dcc.RangeSlider( - id=ElementIds.PSY_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - ), - dcc.Checklist( - id=ElementIds.INVERT_MONTH_PSY, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], - ), - ], + dmc.Group( + [ + dmc.Title("Hour Range", order=5), + dmc.Stack( + dcc.RangeSlider( + id=ElementIds.PSY_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={"always_visible": False}, + ), + flex=1, ), - dmc.Flex( - children=[ - dmc.Text("Hour Range", miw=110), - dmc.Stack( - flex=1, - children=dcc.RangeSlider( - id=ElementIds.PSY_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, - ), - ), - dcc.Checklist( - id=ElementIds.INVERT_HOUR_PSY, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], - ), - ], + dcc.Checklist( + id=ElementIds.INVERT_HOUR_PSY, + options=[{"label": "Invert", "value": "invert"}], + value=[], ), ], ), - dmc.Stack( - children=[ - dmc.Button( - "Apply filter", - id=ElementIds.DATA_FILTER, - variant="filled", - color="blue", - size="md", - fullWidth=True, - ), - dmc.Flex( - children=[ - dmc.Text("Filter Variable:", miw=130), - dmc.Stack( - flex=1, - children=dropdown( - id=ElementIds.PSY_VAR_DROPDOWN, - options=dropdown_names, - value=ColNames.RH, - ), - ), - ], + ], + ), + dmc.Stack( + [ + dmc.Button( + "Apply filter", + id=ElementIds.DATA_FILTER, + color="blue", + ), + dmc.Group( + [ + dmc.Title("Filter Variable:", order=5), + dmc.Stack( + dropdown( + id=ElementIds.PSY_VAR_DROPDOWN, + options=dropdown_names, + value=ColNames.RH, + ), + flex=1, ), - dmc.Flex( - children=[ - dmc.Text("Min Value:", miw=130), - dmc.Stack( - flex=1, - children=dmc.NumberInput( - id=ElementIds.PSY_MIN_VAL, - placeholder="Enter a number for the min val", - value=0, - step=1, - size="md", - ), - ), - ], + ], + ), + dmc.Group( + [ + dmc.Title("Min Value:", order=5), + dmc.Stack( + dmc.NumberInput( + id=ElementIds.PSY_MIN_VAL, + placeholder="Enter a number for the min val", + value=0, + step=1, + ), + flex=1, ), - dmc.Flex( - children=[ - dmc.Text("Max Value:", miw=130), - dmc.Stack( - flex=1, - children=dmc.NumberInput( - id=ElementIds.PSY_MAX_VAL, - placeholder="Enter a number for the max val", - value=100, - step=1, - size="md", - ), - ), - ], + ], + ), + dmc.Group( + [ + dmc.Title("Max Value:", order=5), + dmc.Stack( + dmc.NumberInput( + id=ElementIds.PSY_MAX_VAL, + placeholder="Enter a number for the max val", + value=100, + step=1, + ), + flex=1, ), ], ), @@ -212,6 +185,7 @@ def inputs(): def layout(): return dmc.Stack( + p="md", children=[ title_with_link( text="Psychrometric Chart", @@ -225,7 +199,6 @@ def layout(): inputs(), dmc.Paper( id=ElementIds.PSYCH_CHART, - p="sm", ), ], ), @@ -234,7 +207,6 @@ def layout(): ) -# psychrometric chart @callback( Output(ElementIds.PSYCH_CHART, "children"), [ diff --git a/pages/select.py b/pages/select.py index 4a8e2a4f..60b77de2 100644 --- a/pages/select.py +++ b/pages/select.py @@ -56,7 +56,7 @@ def layout(): id=ElementIds.UPLOAD_DATA_BUTTON, variant="outline", color="gray", - style={"borderStyle": "dashed", "borderRadius": "5px"}, + style={"borderStyle": "dashed"}, styles={"label": {"fontWeight": 400}}, ), # Allow multiple files to be uploaded @@ -89,14 +89,12 @@ def layout(): dmc.Button( "Close", id=ElementIds.MODAL_CLOSE_BUTTON, - ml="sm", color="gray", variant="outline", ), dmc.Button( "Yes", id=ElementIds.MODAL_YES_BUTTON, - ml="sm", color="blue", ), ], @@ -105,9 +103,8 @@ def layout(): ], ), ], - mx=0, - px=0, - py="md", + p="md", + mb="xl", style={ "display": "flex", "flexDirection": "column", @@ -393,5 +390,5 @@ def plot_location_epw_files(pathname): id=ElementIds.TAB_ONE_MAP, figure=fig, config=generate_chart_name(TabNames.EPW_LOCATION_SELECT), - style={"position": "relative", "zIndex": 5}, + # style={"position": "relative", "zIndex": 5}, ) diff --git a/pages/summary.py b/pages/summary.py index f96f237d..037ac7b4 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -37,7 +37,7 @@ def layout(): return dmc.Container( fluid=True, - px="md", + p="md", children=[ dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER), ], @@ -78,8 +78,6 @@ def update_layout(si_ip): dcc.Loading( type="circle", children=dmc.Group( - align="center", - justify="flex-start", children=[ dmc.Button( "Download EPW", @@ -105,7 +103,6 @@ def update_layout(si_ip): ), dmc.Stack(id=ElementIds.WARNING_CDD_HIGHER_HDD), dmc.Group( - align="center", justify="center", children=[ dmc.Text("Heating degree day (HDD) setpoint"), diff --git a/pages/sun.py b/pages/sun.py index 4c9b2aef..dd9d1c47 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -57,7 +57,6 @@ def sun_path(): """Return the layout for the custom sun path and its dropdowns.""" return dmc.Stack( - p="sm", children=[ title_with_link( text="Sun path chart", @@ -104,7 +103,6 @@ def sun_path(): def explore_daily_heatmap(): """Contents of the bottom part of the tab""" return dmc.Stack( - p="sm", w="100%", children=[ title_with_link( @@ -125,12 +123,10 @@ def explore_daily_heatmap(): ), ], ), - dcc.Loading( - type="circle", children=dmc.Stack(id=ElementIds.TAB4_DAILY, w="100%") - ), + dcc.Loading(type="circle", children=dmc.Stack(id=ElementIds.TAB4_DAILY)), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.TAB4_HEATMAP, w="100%"), + children=dmc.Stack(id=ElementIds.TAB4_HEATMAP), ), ], ) @@ -139,7 +135,6 @@ def explore_daily_heatmap(): def static_section(): return dmc.Stack( id=ElementIds.STATIC_SECTION, - p="sm", w="100%", children=[ # ... @@ -150,8 +145,7 @@ def static_section(): def layout(): """Contents of tab four.""" return dmc.Stack( - p="sm", - w="100%", + p="md", id=ElementIds.TAB_FOUR_CONTAINER, children=[sun_path(), static_section(), explore_daily_heatmap()], ) @@ -173,7 +167,7 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.MONTHLY_SOLAR, w="100%"), + children=dmc.Stack(id=ElementIds.MONTHLY_SOLAR), ), title_with_link( text="Cloud coverage", @@ -182,7 +176,7 @@ def update_static_section(si_ip): ), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.CLOUD_COVER, w="100%"), + children=dmc.Stack(id=ElementIds.CLOUD_COVER), ), ] diff --git a/pages/t_rh.py b/pages/t_rh.py index 7d57f698..edd1e726 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -34,14 +34,14 @@ def layout(): return dmc.Container( fluid=True, - px="md", + p="md", children=[ dmc.Group( justify="center", align="center", wrap="nowrap", children=[ - dmc.Text("Select a variable:", fz="xl"), + dmc.Title("Select a variable:", order=5), dropdown( id=ElementIds.ID_T_RH_DROPDOWN, options={var: dropdown_names[var] for var in var_to_plot}, diff --git a/pages/wind.py b/pages/wind.py index e3d5be43..09738699 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -35,7 +35,7 @@ def sliders(): children=[ dmc.Group( children=[ - dmc.Text("Month Range"), + dmc.Title("Month Range", order=5), dcc.RangeSlider( id=ElementIds.MONTH_SLIDER, min=1, @@ -50,7 +50,7 @@ def sliders(): ), dmc.Group( children=[ - dmc.Text("Hour Range"), + dmc.Title("Hour Range", order=5), dcc.RangeSlider( id=ElementIds.HOUR_SLIDER, min=1, @@ -211,8 +211,6 @@ def custom_wind_rose(): ), dmc.Grid( gutter="md", - justify="center", - align="center", maw=900, mx="auto", children=[ @@ -242,7 +240,7 @@ def custom_wind_rose(): dmc.Group( children=[ dmc.Title( - "Start Hour:", order=6, w="8rem", ta="right" + "Start Hour:", order=5, w="8rem", ta="right" ), dropdown( id=ElementIds.TAB5_CUSTOM_START_HOUR, @@ -264,7 +262,7 @@ def custom_wind_rose(): dmc.Group( children=[ dmc.Title( - "End Month:", order=6, w="8rem", ta="right" + "End Month:", order=5, w="8rem", ta="right" ), dropdown( id=ElementIds.TAB5_CUSTOM_END_MONTH, @@ -280,7 +278,7 @@ def custom_wind_rose(): dmc.Group( children=[ dmc.Title( - "End Hour:", order=6, w="8rem", ta="right" + "End Hour:", order=5, w="8rem", ta="right" ), dropdown( id=ElementIds.TAB5_CUSTOM_END_HOUR, @@ -308,6 +306,7 @@ def custom_wind_rose(): def layout(): """Contents in the fifth tab 'Wind'.""" return dmc.Stack( + p="md", children=[ title_with_link( text="Annual Wind Rose", diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index 2adfcbdc..38dea4fe 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -14,7 +14,6 @@ function click_tab(name) { // Open the sidebar (burger button is fixed on screen) - cy.get('#burger-button', { timeout: 10000 }).click({ force: true }); // Expand the main nav group if collapsed cy.get('#nav-group-main').click({ force: true }); // Locate tab item by ID prefix, then find label by text @@ -121,7 +120,7 @@ describe('Clima', () => { click_tab('Temperature and Humidity') // Expand the "Data Display Options" nav section to access controls cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true }); - cy.contains('Global Value Ranges', { timeout: 10000 }).click({ force: true }); + cy.contains('Global', { timeout: 10000 }).click({ force: true }); cy.contains('-40'); // Global minimum: not something you see in Italy! cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true }); cy.contains('IP').click({ force: true }); From 56cad466285bea7a6c4ad7db8f8abfe61f4968cc Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 15:37:26 +1000 Subject: [PATCH 091/163] style(layout): Simplify children structure in ScrollArea Removed unnecessary Stack component to streamline the layout and improve readability. Also optimized spacing in the layout by removing redundant CSS properties. --- assets/construction.css | 5 ---- assets/footer.css | 18 ------------- assets/tabs.css | 57 ----------------------------------------- pages/lib/layout.py | 4 +-- 4 files changed, 1 insertion(+), 83 deletions(-) delete mode 100644 assets/construction.css delete mode 100644 assets/footer.css diff --git a/assets/construction.css b/assets/construction.css deleted file mode 100644 index 930d1086..00000000 --- a/assets/construction.css +++ /dev/null @@ -1,5 +0,0 @@ -#construction-container { - height: 80vh; - align-items: center; - margin-top: 20px; -} \ No newline at end of file diff --git a/assets/footer.css b/assets/footer.css deleted file mode 100644 index b334dcd4..00000000 --- a/assets/footer.css +++ /dev/null @@ -1,18 +0,0 @@ -#footer-container { - padding: 1rem; - margin:0; - color: white; - background-color: #003262; -} - -a { - color: white; - text-decoration: underline; -} - -a:hover { - color: lightgrey; -} - - - diff --git a/assets/tabs.css b/assets/tabs.css index 1813deda..56c05f8e 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -94,63 +94,6 @@ background-color: white; } -/* Tab One */ -#tab-one-container { - height: 100%; - padding-top: 0; -} - -#tab-one-form-container { - justify-content: space-between; -} - -#store-container { - height: 100%; -} - -#tab-one-map { - margin-top: 28px; - /*height: 100vh;*/ - width: 100%; -} - -#map-credits { - padding-top: 10px; -} - - -/* Tab Two */ -#tab-two-container { - justify-content: space-between; - width: 100%; -} - -p { - margin-bottom: 0; -} - -#world-map { - height: 65%; - width: 100%; -} - -.violin-container { - height: 100%; - width: 100%; - display: flex; - align-items: stretch; -} - -.loading-violin-container { - width: 100%; - height: 100%; -} - -#temp-profile-graph, #humidity-profile-graph, #solar-radiation-graph, #wind-speed-graph { - height: 50%; - /*width: 100%;*/ -} - /* Tab Temperature RH*/ .dropdown-t-rh { width: 15rem; diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 2b532fb3..71b11193 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -157,7 +157,7 @@ def create_navbar(): ) return dmc.ScrollArea( - children=dmc.Stack(gap="xs", children=[parent_group, controls_group, doc_link]), + children=[parent_group, controls_group, doc_link], ) @@ -300,10 +300,8 @@ def create_footer(): p="sm", c="white", bg="#003262", - gap="xl", justify="flex-start", align="center", - px="lg", ) From 773bf25a51430a284bee71b709ee5808c92338dd Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 15:37:38 +1000 Subject: [PATCH 092/163] style(select): Refactor layout to use Stack component Replace Box with Stack for improved layout structure and remove unnecessary style properties to simplify the code. --- pages/select.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/pages/select.py b/pages/select.py index 60b77de2..63db2152 100644 --- a/pages/select.py +++ b/pages/select.py @@ -38,7 +38,8 @@ def layout(): """Contents in the first tab 'Select Weather File'""" - return dmc.Box( + return dmc.Stack( + p="md", children=[ dcc.Loading( id=ElementIds.LOADING_ONE, @@ -103,13 +104,6 @@ def layout(): ], ), ], - p="md", - mb="xl", - style={ - "display": "flex", - "flexDirection": "column", - "gap": "var(--mantine-spacing-md)", - }, ) @@ -319,8 +313,8 @@ def display_modal_when_data_clicked(_, click_map, __, opened): url = re.search( r'href=[\'"]?([^\'" >]+)', click_map["points"][0]["customdata"][-1] ).group(1) - return (not opened, url) - return (opened, "") + return not opened, url + return opened, "" @callback( @@ -390,5 +384,4 @@ def plot_location_epw_files(pathname): id=ElementIds.TAB_ONE_MAP, figure=fig, config=generate_chart_name(TabNames.EPW_LOCATION_SELECT), - # style={"position": "relative", "zIndex": 5}, ) From cc3c63bd67ce8c773d985e5fc63d9b76fb4040be Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 15:37:50 +1000 Subject: [PATCH 093/163] refactor(layout): Simplify layout structure in summary and template graphs Consolidate container and stack components for improved readability and maintainability. Adjust grid column spans for responsive design. --- pages/lib/template_graphs.py | 1 + pages/summary.py | 24 ++++++++---------------- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index b62f199b..4916381d 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -71,6 +71,7 @@ def violin(df, var, global_local, si_ip): title=title, title_x=0.5, dragmode=False, + height=400, ) fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True) fig.update_yaxes( diff --git a/pages/summary.py b/pages/summary.py index 037ac7b4..9aaab898 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -35,13 +35,7 @@ def layout(): """Contents in the second tab 'Climate Summary'.""" - return dmc.Container( - fluid=True, - p="md", - children=[ - dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER), - ], - ) + return dmc.Stack(id=ElementIds.TAB_TWO_CONTAINER, p="md") @callback( @@ -63,7 +57,7 @@ def update_layout(si_ip): type="circle", children=dmc.Stack( id=ElementIds.LOCATION_INFO, - p="sm", + gap=0, ), ), dcc.Loading( @@ -146,10 +140,10 @@ def update_layout(si_ip): id=ElementIds.GRAPH_CONTAINER, gutter="md", children=[ - dmc.GridCol(id=ElementIds.TEMP_PROFILE_GRAPH, span=3), - dmc.GridCol(id=ElementIds.HUMIDITY_PROFILE_GRAPH, span=3), - dmc.GridCol(id=ElementIds.SOLAR_RADIATION_GRAPH, span=3), - dmc.GridCol(id=ElementIds.WIND_SPEED_GRAPH, span=3), + dmc.GridCol(id=ElementIds.TEMP_PROFILE_GRAPH, span={"base": 12, "sm": 6, "lg": 3}), + dmc.GridCol(id=ElementIds.HUMIDITY_PROFILE_GRAPH, span={"base": 12, "sm": 6, "lg": 3}), + dmc.GridCol(id=ElementIds.SOLAR_RADIATION_GRAPH, span={"base": 12, "sm": 6, "lg": 3}), + dmc.GridCol(id=ElementIds.WIND_SPEED_GRAPH, span={"base": 12, "sm": 6, "lg": 3}), ], ), ], @@ -242,8 +236,7 @@ def update_location_info(ts, df, meta, si_ip): hottest_yearly_tmp = f"Hottest yearly temperature (99%): {df[ColNames.DBT].quantile(0.99).round(1)} {tmp_unit}" coldest_yearly_tmp = f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} {tmp_unit}" - return dmc.Stack( - children=[ + return [ dmc.Text(location, fw=700), dmc.Text(lon), dmc.Text(lat), @@ -255,8 +248,7 @@ def update_location_info(ts, df, meta, si_ip): dmc.Text(coldest_yearly_tmp), dmc.Text(total_solar_rad), dmc.Text(total_diffuse_rad), - ], - ) + ] @callback( From 0807032bcb865ff08fa5975156bc8db3a7ec670c Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 15:38:00 +1000 Subject: [PATCH 094/163] style(utils): Remove unnecessary margin and padding in tooltip functions Clean up the layout by eliminating redundant margin and padding properties in the title_with_tooltip function to streamline the component's styling. --- pages/lib/utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pages/lib/utils.py b/pages/lib/utils.py index ff44face..d31af407 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -149,8 +149,6 @@ def generate_custom_inputs_psy( def title_with_tooltip(text, tooltip_text, id_button): if tooltip_text: return dmc.Group( - mt="md", - px="md", children=[ dmc.Title(text, order=3), dmc.Tooltip( @@ -186,8 +184,6 @@ def title_with_link( doc_link: str = "", ): return dmc.Group( - mt="md", - px="md", children=[ dmc.Title(text, order=3), dmc.Tooltip( From 23eaa741d27c0f3f987dc6797bd3ede621876f62 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 15:45:20 +1000 Subject: [PATCH 095/163] refactor(layout): Replace Container with Stack for layout consistency Updated the layout function to utilize the Stack component instead of Container, improving the structure and alignment of child elements. --- assets/tabs.css | 5 --- pages/summary.py | 44 ++++++++++++++--------- pages/t_rh.py | 91 ++++++++++++++++++++++-------------------------- 3 files changed, 69 insertions(+), 71 deletions(-) diff --git a/assets/tabs.css b/assets/tabs.css index 56c05f8e..b5e7b14c 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -94,11 +94,6 @@ background-color: white; } -/* Tab Temperature RH*/ -.dropdown-t-rh { - width: 15rem; -} - /* Tab Four */ #tab-four-container { width: 100%; diff --git a/pages/summary.py b/pages/summary.py index 9aaab898..d8da56d3 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -140,10 +140,22 @@ def update_layout(si_ip): id=ElementIds.GRAPH_CONTAINER, gutter="md", children=[ - dmc.GridCol(id=ElementIds.TEMP_PROFILE_GRAPH, span={"base": 12, "sm": 6, "lg": 3}), - dmc.GridCol(id=ElementIds.HUMIDITY_PROFILE_GRAPH, span={"base": 12, "sm": 6, "lg": 3}), - dmc.GridCol(id=ElementIds.SOLAR_RADIATION_GRAPH, span={"base": 12, "sm": 6, "lg": 3}), - dmc.GridCol(id=ElementIds.WIND_SPEED_GRAPH, span={"base": 12, "sm": 6, "lg": 3}), + dmc.GridCol( + id=ElementIds.TEMP_PROFILE_GRAPH, + span={"base": 12, "sm": 6, "lg": 3}, + ), + dmc.GridCol( + id=ElementIds.HUMIDITY_PROFILE_GRAPH, + span={"base": 12, "sm": 6, "lg": 3}, + ), + dmc.GridCol( + id=ElementIds.SOLAR_RADIATION_GRAPH, + span={"base": 12, "sm": 6, "lg": 3}, + ), + dmc.GridCol( + id=ElementIds.WIND_SPEED_GRAPH, + span={"base": 12, "sm": 6, "lg": 3}, + ), ], ), ], @@ -237,18 +249,18 @@ def update_location_info(ts, df, meta, si_ip): coldest_yearly_tmp = f"Coldest yearly temperature (1%): {df[ColNames.DBT].quantile(0.01).round(1)} {tmp_unit}" return [ - dmc.Text(location, fw=700), - dmc.Text(lon), - dmc.Text(lat), - dmc.Text(elevation), - dmc.Text(period) if period else None, - dmc.Text(climate_text) if climate_text else None, - dmc.Text(average_yearly_tmp), - dmc.Text(hottest_yearly_tmp), - dmc.Text(coldest_yearly_tmp), - dmc.Text(total_solar_rad), - dmc.Text(total_diffuse_rad), - ] + dmc.Text(location, fw=700), + dmc.Text(lon), + dmc.Text(lat), + dmc.Text(elevation), + dmc.Text(period) if period else None, + dmc.Text(climate_text) if climate_text else None, + dmc.Text(average_yearly_tmp), + dmc.Text(hottest_yearly_tmp), + dmc.Text(coldest_yearly_tmp), + dmc.Text(total_solar_rad), + dmc.Text(total_diffuse_rad), + ] @callback( diff --git a/pages/t_rh.py b/pages/t_rh.py index edd1e726..39d38993 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -32,66 +32,57 @@ def layout(): - return dmc.Container( - fluid=True, + return dmc.Stack( p="md", children=[ - dmc.Group( - justify="center", - align="center", - wrap="nowrap", - children=[ - dmc.Title("Select a variable:", order=5), + dmc.Center( + [ + dmc.Title("Select a variable:", order=5, mr="md"), dropdown( id=ElementIds.ID_T_RH_DROPDOWN, options={var: dropdown_names[var] for var in var_to_plot}, value=dropdown_names[var_to_plot[0]], style={"width": "14rem"}, ), - ], + ] ), - dmc.Stack( - mt="md", - children=[ - # Yearly Chart - title_with_link( - text="Yearly Chart", - id_button=IdButtons.YEARLY_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), - dcc.Loading( - type="circle", - children=dmc.Stack(id=ElementIds.YEARLY_CHART), - ), - # Daily chart - title_with_link( - text="Daily chart", - id_button=IdButtons.DAILY_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), - dcc.Loading( - type="circle", - children=dmc.Stack(id=ElementIds.DAILY), - ), - # Heatmap chart - title_with_link( - text="Heatmap chart", - id_button=IdButtons.HEATMAP_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), - dcc.Loading( - type="circle", - children=dmc.Stack(id=ElementIds.HEATMAP), - ), - # Descriptive statistics - title_with_tooltip( - text="Descriptive statistics", - tooltip_text="count, mean, std, min, max, and percentiles", - id_button=IdButtons.TABLE_TMP_RH, - ), - dmc.Stack(id=ElementIds.TABLE_TMP_HUM), - ], + # Yearly Chart + title_with_link( + text="Yearly Chart", + id_button=IdButtons.YEARLY_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + ), + dcc.Loading( + type="circle", + children=dmc.Stack(id=ElementIds.YEARLY_CHART), + ), + # Daily chart + title_with_link( + text="Daily chart", + id_button=IdButtons.DAILY_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + ), + dcc.Loading( + type="circle", + children=dmc.Stack(id=ElementIds.DAILY), + ), + # Heatmap chart + title_with_link( + text="Heatmap chart", + id_button=IdButtons.HEATMAP_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + ), + dcc.Loading( + type="circle", + children=dmc.Stack(id=ElementIds.HEATMAP), + ), + # Descriptive statistics + title_with_tooltip( + text="Descriptive statistics", + tooltip_text="count, mean, std, min, max, and percentiles", + id_button=IdButtons.TABLE_TMP_RH, ), + dmc.Stack(id=ElementIds.TABLE_TMP_HUM), ], ) From 178f062a54faa1381a6de13929b8e2132d998bea Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 15:49:16 +1000 Subject: [PATCH 096/163] refactor(layout): Move layout function to improve structure Refactor the layout function for tab four by removing the old definition and ensuring the layout is consistently defined using the Stack component. --- assets/tabs.css | 13 ------------- pages/sun.py | 18 +++++++++--------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/assets/tabs.css b/assets/tabs.css index b5e7b14c..7a20c9df 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -94,19 +94,6 @@ background-color: white; } -/* Tab Four */ -#tab-four-container { - width: 100%; - justify-content: center; - align-items: center; -} - -/*#tab-four-custom-sun-container {*/ -/* justify-content: space-evenly;*/ -/* align-items: stretch;*/ -/* align-content: center;*/ -/*}*/ - /* Tab Five */ #tab-five-container { justify-content: center; diff --git a/pages/sun.py b/pages/sun.py index dd9d1c47..afd3dd86 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -54,6 +54,15 @@ sc_dropdown_names.pop("UTCI: no Sun & no Wind : categories", None) +def layout(): + """Contents of tab four.""" + return dmc.Stack( + p="md", + id=ElementIds.TAB_FOUR_CONTAINER, + children=[sun_path(), static_section(), explore_daily_heatmap()], + ) + + def sun_path(): """Return the layout for the custom sun path and its dropdowns.""" return dmc.Stack( @@ -142,15 +151,6 @@ def static_section(): ) -def layout(): - """Contents of tab four.""" - return dmc.Stack( - p="md", - id=ElementIds.TAB_FOUR_CONTAINER, - children=[sun_path(), static_section(), explore_daily_heatmap()], - ) - - @callback( Output(ElementIds.STATIC_SECTION, "children"), [Input(ElementIds.ID_SUN_SI_IP_RADIO_INPUT, "value")], From fd6ede85b7c85b0bb8eb46c3307b2a19acce620b Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 15:54:22 +1000 Subject: [PATCH 097/163] refactor(layout): Update loading component structure Replace dcc.Loading with dmc.Center for improved layout consistency. Remove unused CSS styles related to tabs for cleaner codebase. --- assets/tabs.css | 145 ------------------------------------------------ pages/sun.py | 5 +- 2 files changed, 3 insertions(+), 147 deletions(-) diff --git a/assets/tabs.css b/assets/tabs.css index 7a20c9df..594ba325 100644 --- a/assets/tabs.css +++ b/assets/tabs.css @@ -9,151 +9,6 @@ height: 10rem } -/* Tabs */ -#tabs { - margin: 0; -} - -#tabs-content { - padding: 1rem; -} - -#tabs-parent { - padding: 0 1rem; - background-color: #003262 !important; -} - -#store-container { - padding: 0; -} - -#loading-container { - top: 0; -} - -.custom-tabs-container { - width: 85%; -} - -.custom-tabs { - background-color: #f9f9f9; - padding: 0 24px; - border-bottom: 1px solid #d6d6d6; -} - -.custom-tab { - border-color: rgb(238, 236, 236); - border-top-left-radius: 3px; - border-top-right-radius: 3px; - border-top: 3px solid transparent !important; - border-left: 1px solid lightgrey !important; - border-right: 1px solid lightgrey !important; - border-bottom: 1px solid #d6d6d6; - background-color: #f6f8f8; - padding: 12px !important; - font-family: "system-ui"; - display: flex !important; - align-items: center; - justify-content: center; -} - -.custom-tab:has(.active) { - color:#586069; - background-color: white; - box-shadow: 1px 1px 0 white; - border-left: 1px solid lightgrey !important; - border-right: 1px solid lightgrey !important; - border-top: 6px solid #abd2ff !important; - border-bottom: 1px solid transparent; -} - -.nav-pills { - display: flex; - flex-wrap: wrap; -} - -@media (max-width: 900px) { - .nav-pills { - flex-direction: column; - } -} - -.nav-pills .nav-link { - padding: 0; - color: #586069; - font-family: "system-ui"; - background-color: transparent; -} - -.nav-pills .nav-link.disabled { - color: #c8c6c6; -} - -.nav-pills .nav-link.active { - color: black; - background-color: white; -} - -/* Tab Five */ -#tab-five-container { - justify-content: center; - align-items: stretch; -} - -#tab5-daily-container { - margin-top: 60px; -} - -#slider-container { - width: 50%; - margin: 24px; -} - -.seasonal-graph { - width: 100%; - height: 100%; -} - -.daily-wind-graph { - width: 80%; - height: 80%; -} - -#daily-wind-rose-container { - display: flex; - flex-direction: row; - max-width: 100%; -} - -#daily-wind-rose-outer-container { - align-items: stretch; -} - -#slider-container * { - justify-content: center; -} - -.each-slider { - padding: 32px 0 0 0; - width: 60%; -} - -#hour-slider, #month-slider { - width: 60%; -} - -#wind-speed, #wind-direction { - width: 100%; -} - -#custom-windrose-container { - margin-top: 80px; -} - -#tab5-custom-dropdown-container { - margin-top: 30px; -} - /* Tab Six */ #first-var-dropdown { diff --git a/pages/sun.py b/pages/sun.py index afd3dd86..ddccc4c5 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -101,10 +101,11 @@ def sun_path(): ), ], ), - dcc.Loading( + dmc.Center(dcc.Loading( type="circle", children=dmc.Stack(id=ElementIds.CUSTOM_SUNPATH, w="100%"), - ), + ),), + ], ) From 25131f71c9243c77bf94844e6bbee89711bb2752 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 16:00:52 +1000 Subject: [PATCH 098/163] refactor(layout): Replace Box with Stack for tab six layout Refactor the layout function to use a Stack component instead of Box, improving the structure and consistency of the layout in tab six. --- pages/explorer.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index c6162dc9..ab8937d1 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -60,6 +60,14 @@ explore_dropdown_names.pop("None", None) +def layout(): + """Return the contents of tab six.""" + return dmc.Stack( + p="md", + children=[*section_one(), section_two(), section_three()], + ) + + def section_one_inputs(): """Return the inputs from section one.""" return dmc.Group( @@ -78,8 +86,7 @@ def section_one_inputs(): def section_one(): """Return the graphs for section one""" - return dmc.Stack( - children=[ + return [ section_one_inputs(), title_with_link( text="Yearly chart", @@ -182,8 +189,7 @@ def section_one(): ), # Results table dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"), - ], - ) + ] def section_two_inputs(): @@ -548,13 +554,6 @@ def section_three(): ) -def layout(): - """Return the contents of tab six.""" - return dmc.Box( - children=[section_one(), section_two(), section_three()], - ) - - @callback( Output(ElementIds.YEARLY_EXPLORE, "children"), # Section One From 8c873bce76f3bd8b17cdb6bedc46b5cb228df20d Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 16:01:11 +1000 Subject: [PATCH 099/163] feat(css): remove tabs.css --- assets/tabs.css | 69 ------------------------------------------------- 1 file changed, 69 deletions(-) delete mode 100644 assets/tabs.css diff --git a/assets/tabs.css b/assets/tabs.css deleted file mode 100644 index 594ba325..00000000 --- a/assets/tabs.css +++ /dev/null @@ -1,69 +0,0 @@ -*[data-dash-is-loading="true"]{ - visibility: hidden; -} -*[data-dash-is-loading="true"]::before{ - content: ""; - display: inline-block; - color: magenta; - visibility: visible; - height: 10rem -} - -/* Tab Six */ - -#first-var-dropdown { - width: 15%; -} - -.month-hour-slider { - width: 25%; -} - -.var-dropdown { - width: 35%; -} - -#min-val, #max-val { - width: 15%; -} - -.row-center { - align-items: center; - justify-content: center; -} - -.text-next-to-input { - margin-bottom: 0; - margin-right: 1rem; - text-align: right; -} - -#sec1-var-dropdown { - width: 25%; -} - -.three-inputs-container { - justify-content: space-evenly; -} - -.one-of-three-container { - width: 30%; - align-self: flex-start; - align-items: stretch; -} - -#tab6-sec2-container { - align-items: stretch; -} - -.survey-alert { - color: white; - background-color: #0c2772; - opacity: 0.98; - font-family: "system-ui"; - font-size: 15px; - border-radius: 0.25rem; - border: 0.5px solid lightgrey; - z-index: 1000; -} - From 0684fc4ab1b48cb022a21aa75cba99b091e6d45e Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 16:02:41 +1000 Subject: [PATCH 100/163] refactor(viz): Remove y-axis range setting hhd and ccd Eliminate fixed y-axis range to allow for dynamic scaling based on data. --- pages/summary.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pages/summary.py b/pages/summary.py index d8da56d3..7f11a30e 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -338,7 +338,6 @@ def degree_day_chart(ts, n_clicks, df, meta, hdd_value, cdd_value, si_ip): template=template, dragmode=False, legend=dict(orientation="h", yanchor="bottom", y=1.05, xanchor="right", x=1), - yaxis=dict(range=[-100, 400]), ) fig.update_xaxes(showline=True, linewidth=1, linecolor="black", mirror=True) fig.update_yaxes(showline=True, linewidth=1, linecolor="black", mirror=True) From 93198f1fb408b4c87568db486c7622a5ad7b7bae Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 16:09:34 +1000 Subject: [PATCH 101/163] refactor(ui): Remove inline styles from dropdown components Clean up the code by removing hardcoded width styles from various dropdowns to improve layout consistency and maintainability. --- pages/lib/utils.py | 1 + pages/sun.py | 3 --- pages/t_rh.py | 1 - pages/wind.py | 4 ---- 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/pages/lib/utils.py b/pages/lib/utils.py index d31af407..c95c9084 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -278,6 +278,7 @@ def dropdown(options=None, **kwargs): return dcc.Dropdown( options=[{"label": k, "value": v} for k, v in options.items()], clearable=False, + style={"width": "14rem"}, **kwargs, ) diff --git a/pages/sun.py b/pages/sun.py index ddccc4c5..222f5cc1 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -84,7 +84,6 @@ def sun_path(): "Cartesian": "cartesian", }, value="polar", - style={"width": "10rem"}, ), ], ), @@ -97,7 +96,6 @@ def sun_path(): id=ElementIds.CUSTOM_SUN_VAR_DROPDOWN, options=sc_dropdown_names, value="None", - style={"width": "20rem"}, ), ], ), @@ -129,7 +127,6 @@ def explore_daily_heatmap(): id=ElementIds.TAB_EXPLORE_DROPDOWN, options=sun_cloud_tab_explore_dropdown_names, value="glob_hor_rad", - style={"width": "20rem"}, ), ], ), diff --git a/pages/t_rh.py b/pages/t_rh.py index 39d38993..a9c01b63 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -42,7 +42,6 @@ def layout(): id=ElementIds.ID_T_RH_DROPDOWN, options={var: dropdown_names[var] for var in var_to_plot}, value=dropdown_names[var_to_plot[0]], - style={"width": "14rem"}, ), ] ), diff --git a/pages/wind.py b/pages/wind.py index 09738699..195f4b6f 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -233,7 +233,6 @@ def custom_wind_rose(): for i, j in enumerate(month_lst) }, value=1, - style={"width": "6rem"}, ), ], ), @@ -248,7 +247,6 @@ def custom_wind_rose(): str(i) + ":00": i for i in range(0, 24) }, value=0, - style={"width": "6rem"}, ), ], ), @@ -271,7 +269,6 @@ def custom_wind_rose(): for i, j in enumerate(month_lst) }, value=12, - style={"width": "6rem"}, ), ], ), @@ -286,7 +283,6 @@ def custom_wind_rose(): str(i) + ":00": i for i in range(1, 25) }, value=24, - style={"width": "6rem"}, ), ], ), From 0ce33652848d68438091d09a2f66e8cfc9869b18 Mon Sep 17 00:00:00 2001 From: federico tartarini Date: Fri, 26 Sep 2025 16:11:44 +1000 Subject: [PATCH 102/163] refactor(explorer): Clean up section one layout code Reorganize the layout code in section one for better readability and maintainability. This includes consistent indentation and formatting adjustments. --- pages/explorer.py | 198 +++++++++++++++++++++++----------------------- pages/sun.py | 11 +-- 2 files changed, 103 insertions(+), 106 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index ab8937d1..f40b4e8c 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -87,109 +87,105 @@ def section_one_inputs(): def section_one(): """Return the graphs for section one""" return [ - section_one_inputs(), - title_with_link( - text="Yearly chart", - id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), - dcc.Loading( - type="circle", children=dmc.Paper(id=ElementIds.YEARLY_EXPLORE, p="sm") - ), - title_with_link( - text="Daily chart", - id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), - dcc.Loading( - type="circle", children=dmc.Paper(id=ElementIds.QUERY_DAILY, p="sm") - ), - title_with_link( - text="Heatmap chart", - id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL, - doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, - ), - dcc.Loading( - type="circle", children=dmc.Paper(id=ElementIds.QUERY_HEATMAP, p="sm") - ), - title_with_tooltip( - text="Descriptive statistics", - tooltip_text="count, mean, std, min, max, and percentiles", - id_button=IdButtons.TABLE_EXPLORE, - ), - dmc.Center( - children=dmc.Box( - w="33%", - children=dmc.Stack( - children=[ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.SEC1_TIME_FILTER_INPUT, - color="blue", - ), - dmc.Group( - children=[ - dmc.Title("Month Range", order=5), - dmc.Stack( - flex=1, - children=dcc.RangeSlider( - id=ElementIds.SEC1_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - ), - dcc.Checklist( - id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], - ), - ], - ), - dmc.Group( - children=[ - dmc.Title("Hour Range", order=5), - dmc.Stack( - flex=1, - children=dcc.RangeSlider( - id=ElementIds.SEC1_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, - ), + section_one_inputs(), + title_with_link( + text="Yearly chart", + id_button=IdButtons.EXPLORE_YEARLY_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + ), + dcc.Loading( + type="circle", children=dmc.Paper(id=ElementIds.YEARLY_EXPLORE, p="sm") + ), + title_with_link( + text="Daily chart", + id_button=IdButtons.EXPLORE_DAILY_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + ), + dcc.Loading( + type="circle", children=dmc.Paper(id=ElementIds.QUERY_DAILY, p="sm") + ), + title_with_link( + text="Heatmap chart", + id_button=IdButtons.EXPLORE_HEATMAP_CHART_LABEL, + doc_link=DocLinks.TEMP_HUMIDITY_EXPLAINED, + ), + dcc.Loading( + type="circle", children=dmc.Paper(id=ElementIds.QUERY_HEATMAP, p="sm") + ), + title_with_tooltip( + text="Descriptive statistics", + tooltip_text="count, mean, std, min, max, and percentiles", + id_button=IdButtons.TABLE_EXPLORE, + ), + dmc.Center( + children=dmc.Box( + w="33%", + children=dmc.Stack( + children=[ + dmc.Button( + "Apply month and hour filter", + id=ElementIds.SEC1_TIME_FILTER_INPUT, + color="blue", + ), + dmc.Group( + children=[ + dmc.Title("Month Range", order=5), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.SEC1_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, ), - dcc.Checklist( - id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], + ), + dcc.Checklist( + id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, + options=[{"label": "Invert", "value": "invert"}], + value=[], + ), + ], + ), + dmc.Group( + children=[ + dmc.Title("Hour Range", order=5), + dmc.Stack( + flex=1, + children=dcc.RangeSlider( + id=ElementIds.SEC1_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "topLeft", + }, + allowCross=False, ), - ], - ), - ], - ), - ) - ), - # Results table - dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"), - ] + ), + dcc.Checklist( + id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, + options=[{"label": "Invert", "value": "invert"}], + value=[], + ), + ], + ), + ], + ), + ) + ), + # Results table + dmc.Paper(id=ElementIds.TABLE_DATA_EXPLORER, p="sm"), + ] def section_two_inputs(): diff --git a/pages/sun.py b/pages/sun.py index 222f5cc1..5307acc2 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -99,11 +99,12 @@ def sun_path(): ), ], ), - dmc.Center(dcc.Loading( - type="circle", - children=dmc.Stack(id=ElementIds.CUSTOM_SUNPATH, w="100%"), - ),), - + dmc.Center( + dcc.Loading( + type="circle", + children=dmc.Stack(id=ElementIds.CUSTOM_SUNPATH, w="100%"), + ), + ), ], ) From ec8441871b1fb0f896e53ffc98bcd4cfa999f64d Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Mon, 29 Sep 2025 19:34:07 +1000 Subject: [PATCH 103/163] refactor: unify ColNames and mapping_dictionary into VariableInfo (fixes #245) --- pages/explorer.py | 41 +- pages/lib/charts_data_explorer.py | 60 ++- pages/lib/charts_summary.py | 12 +- pages/lib/charts_sun.py | 155 +++--- pages/lib/extract_df.py | 367 +++++++------ pages/lib/global_column_names.py | 120 ----- pages/lib/global_scheme.py | 835 +++--------------------------- pages/lib/global_variables.py | 663 ++++++++++++++++++++++++ pages/lib/layout.py | 20 +- pages/lib/template_graphs.py | 232 ++++++--- pages/lib/utils.py | 112 ++-- pages/natural_ventilation.py | 72 ++- pages/outdoor.py | 10 +- pages/psy-chart.py | 70 +-- pages/select.py | 32 +- pages/summary.py | 64 +-- pages/sun.py | 4 +- pages/t_rh.py | 67 +-- pages/wind.py | 54 +- 19 files changed, 1533 insertions(+), 1457 deletions(-) delete mode 100644 pages/lib/global_column_names.py create mode 100644 pages/lib/global_variables.py diff --git a/pages/explorer.py b/pages/explorer.py index f40b4e8c..2614d108 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -13,7 +13,7 @@ three_var_graph, ) from pages.lib.global_element_ids import ElementIds -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames from pages.lib.global_scheme import ( @@ -93,25 +93,19 @@ def section_one(): 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") - ), + dcc.Loading(type="circle", children=dmc.Paper(id=ElementIds.YEARLY_EXPLORE)), 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") - ), + dcc.Loading(type="circle", children=dmc.Paper(id=ElementIds.QUERY_DAILY)), 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") - ), + dcc.Loading(type="circle", children=dmc.Paper(id=ElementIds.QUERY_HEATMAP)), title_with_tooltip( text="Descriptive statistics", tooltip_text="count, mean, std, min, max, and percentiles", @@ -209,7 +203,7 @@ def section_two_inputs(): dropdown( id=ElementIds.SEC2_VAR_DROPDOWN, options=explore_dropdown_names, - value=ColNames.RH, + value=Variables.RH.col_name, ), flex=1, ), @@ -293,7 +287,7 @@ def section_two_inputs(): dropdown( id=ElementIds.SEC2_DATA_FILTER_VAR, options=explore_dropdown_names, - value=ColNames.RH, + value=Variables.RH.col_name, ), flex=1, ), @@ -396,7 +390,7 @@ def section_three_inputs(): dropdown( id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, options=explore_dropdown_names, - value=ColNames.RH, + value=Variables.RH.col_name, ), flex=1, ), @@ -484,7 +478,7 @@ def section_three_inputs(): dropdown( id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, options=explore_dropdown_names, - value=ColNames.RH, + value=Variables.RH.col_name, ), flex=1, ), @@ -536,14 +530,12 @@ def section_three(): type="circle", children=dmc.Paper( id=ElementIds.THREE_VAR, - p="sm", ), ), dcc.Loading( type="circle", children=dmc.Paper( id=ElementIds.TWO_VAR, - p="sm", ), ), ], @@ -888,13 +880,20 @@ def update_table( ) filtered_df = df[ - (df[ColNames.MONTH] >= start_month) - & (df[ColNames.MONTH] <= end_month) - & (df[ColNames.HOUR] >= start_hour) - & (df[ColNames.HOUR] <= end_hour) + (df[Variables.MONTH.col_name] >= start_month) + & (df[Variables.MONTH.col_name] <= end_month) + & (df[Variables.HOUR.col_name] >= start_hour) + & (df[Variables.HOUR.col_name] <= end_hour) ] return summary_table_tmp_rh_tab( - filtered_df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], + filtered_df[ + [ + Variables.MONTH.col_name, + Variables.HOUR.col_name, + dd_value, + Variables.MONTH_NAMES.col_name, + ] + ], dd_value, si_ip, ) diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index 4101271e..f8541d55 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -2,8 +2,8 @@ import plotly.express as px import plotly.graph_objects as go from pages.lib.utils import get_max_min_value -from pages.lib.global_scheme import template, mapping_dictionary, month_lst -from pages.lib.global_column_names import ColNames +from pages.lib.global_scheme import template, month_lst +from pages.lib.global_variables import Variables, VariableInfo def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si_ip): @@ -29,12 +29,16 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si if df.dropna(subset=[var]).shape[0] == 0: return None - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_name = mapping_dictionary[var][ColNames.NAME] - var_color = mapping_dictionary[var][ColNames.COLOR] - filter_name = mapping_dictionary[filter_var][ColNames.NAME] - filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT] + variable = VariableInfo.from_col_name(var) + filter_variable = VariableInfo.from_col_name(filter_var) + + var_name = variable.get_name() + var_unit = variable.get_unit(si_ip) + var_range = variable.get_range(si_ip) + var_color = variable.get_color() + + filter_name = filter_variable.get_name() + filter_unit = filter_variable.get_unit(si_ip) if global_local == "global": # Set Global values for Max and minimum @@ -58,15 +62,18 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si fig = go.Figure( data=go.Heatmap( - y=df[ColNames.HOUR], - x=df[ColNames.DOY], + y=df[Variables.HOUR.col_name], + x=df[Variables.DOY.col_name], z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], connectgaps=False, hoverongaps=False, - customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), + customdata=np.stack( + (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), hovertemplate=( "" + var @@ -109,12 +116,16 @@ def three_var_graph( min_val = data_filter_info3[2] max_val = data_filter_info3[3] - var_unit_x = mapping_dictionary[var_x][si_ip][ColNames.UNIT] - var_unit_y = mapping_dictionary[var_y][si_ip][ColNames.UNIT] + variable_x = VariableInfo.from_col_name(var_x) + variable_y = VariableInfo.from_col_name(var_y) + variable_color = VariableInfo.from_col_name(color_by) + + var_unit_x = variable_x.get_unit(si_ip) + var_unit_y = variable_y.get_unit(si_ip) + var_range = variable_color.get_range(si_ip) + var_color = variable_color.get_color() var = color_by - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_color = mapping_dictionary[var][ColNames.COLOR] if global_local != "global": # Set maximum and minimum according to data @@ -129,15 +140,15 @@ def three_var_graph( else: df.loc[(df[filter_var] >= max_val) & (df[filter_var] <= min_val)] = None - if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: + if df.dropna(subset=[Variables.MONTH.col_name]).shape[0] == 0: return None title = ( - mapping_dictionary[var_x][ColNames.NAME] + variable_x.get_name() + " vs " - + mapping_dictionary[var_y][ColNames.NAME] + + variable_y.get_name() + " colored by " - + mapping_dictionary[color_by][ColNames.NAME] + + variable_color.get_name() ) fig = px.scatter( @@ -162,15 +173,18 @@ def three_var_graph( def two_var_graph(df, var_x, var_y, si_ip): + variable_x = VariableInfo.from_col_name(var_x) + variable_y = VariableInfo.from_col_name(var_y) + title = ( "Simultaneous frequency of " - + mapping_dictionary[var_x][ColNames.NAME] + + variable_x.get_name() + " and " - + mapping_dictionary[var_y][ColNames.NAME] + + variable_y.get_name() ) - var_unit_x = mapping_dictionary[var_x][si_ip][ColNames.UNIT] - var_unit_y = mapping_dictionary[var_y][si_ip][ColNames.UNIT] + var_unit_x = variable_x.get_unit(si_ip) + var_unit_y = variable_y.get_unit(si_ip) fig = px.density_heatmap( df, diff --git a/pages/lib/charts_summary.py b/pages/lib/charts_summary.py index d48a0a51..be3eb9be 100644 --- a/pages/lib/charts_summary.py +++ b/pages/lib/charts_summary.py @@ -1,15 +1,15 @@ import pandas as pd import plotly.express as px -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables def world_map(meta): """Return the world map showing the current location.""" - latitude = float(meta[ColNames.LAT]) - longitude = float(meta[ColNames.LON]) - city = meta[ColNames.CITY] - country = meta[ColNames.COUNTRY] - time_zone = float(meta[ColNames.TIME_ZONE]) + latitude = float(meta[Variables.LAT.col_name]) + longitude = float(meta[Variables.LON.col_name]) + city = meta[Variables.CITY.col_name] + country = meta[Variables.COUNTRY.col_name] + time_zone = float(meta[Variables.TIME_ZONE.col_name]) lat_long_df = pd.DataFrame( data={ "Lat": [latitude], diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index 9bd9747c..b476636c 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -9,24 +9,27 @@ from pages.lib.utils import get_max_min_value from pages.lib.global_scheme import ( template, - mapping_dictionary, degrees_unit, tight_margins, month_lst, ) from plotly.subplots import make_subplots from pvlib import solarposition -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables, VariableInfo def monthly_solar(epw_df, si_ip): g_h_rad_month_ave = ( - epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.GLOB_HOR_RAD] + epw_df.groupby([Variables.MONTH.col_name, Variables.HOUR.col_name])[ + Variables.GLOB_HOR_RAD.col_name + ] .median() .reset_index() ) dif_h_rad_month_ave = ( - epw_df.groupby([ColNames.MONTH, ColNames.HOUR])[ColNames.DIF_HOR_RAD] + epw_df.groupby([Variables.MONTH.col_name, Variables.HOUR.col_name])[ + Variables.DIF_HOR_RAD.col_name + ] .median() .reset_index() ) @@ -44,10 +47,12 @@ def monthly_solar(epw_df, si_ip): fig.add_trace( go.Scatter( x=g_h_rad_month_ave.loc[ - g_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR + g_h_rad_month_ave[Variables.MONTH.col_name] == i + 1, + Variables.HOUR.col_name, ], y=g_h_rad_month_ave.loc[ - g_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.GLOB_HOR_RAD + g_h_rad_month_ave[Variables.MONTH.col_name] == i + 1, + Variables.GLOB_HOR_RAD.col_name, ], fill="tozeroy", mode="lines", @@ -56,13 +61,16 @@ def monthly_solar(epw_df, si_ip): name="Global", showlegend=is_first, customdata=epw_df.loc[ - epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES + epw_df[Variables.MONTH.col_name] == i + 1, + Variables.MONTH_NAMES.col_name, ], hovertemplate=( "" + "Global Horizontal Solar Radiation" + ": %{y:.2f} " - + mapping_dictionary[ColNames.GLOB_HOR_RAD][si_ip][ColNames.UNIT] + + VariableInfo.from_col_name( + Variables.GLOB_HOR_RAD.col_name + ).get_unit(si_ip) + "
" + "Month: %{customdata}
" + "Hour: %{x}:00
" @@ -76,10 +84,12 @@ def monthly_solar(epw_df, si_ip): fig.add_trace( go.Scatter( x=dif_h_rad_month_ave.loc[ - dif_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR + dif_h_rad_month_ave[Variables.MONTH.col_name] == i + 1, + Variables.HOUR.col_name, ], y=dif_h_rad_month_ave.loc[ - dif_h_rad_month_ave[ColNames.MONTH] == i + 1, ColNames.DIF_HOR_RAD + dif_h_rad_month_ave[Variables.MONTH.col_name] == i + 1, + Variables.DIF_HOR_RAD.col_name, ], fill="tozeroy", mode="lines", @@ -88,13 +98,16 @@ def monthly_solar(epw_df, si_ip): name="Diffuse", showlegend=is_first, customdata=epw_df.loc[ - epw_df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES + epw_df[Variables.MONTH.col_name] == i + 1, + Variables.MONTH_NAMES.col_name, ], hovertemplate=( "" + "Diffuse Horizontal Solar Radiation" + ": %{y:.2f} " - + mapping_dictionary[ColNames.DIF_HOR_RAD][si_ip][ColNames.UNIT] + + VariableInfo.from_col_name( + Variables.DIF_HOR_RAD.col_name + ).get_unit(si_ip) + "
" + "Month: %{customdata}
" + "Hour: %{x}:00
" @@ -121,16 +134,16 @@ def monthly_solar(epw_df, si_ip): def polar_graph(df, meta, global_local, var, si_ip): """Return the figure for the custom sun path.""" - latitude = float(meta[ColNames.LAT]) - longitude = float(meta[ColNames.LON]) - time_zone = float(meta[ColNames.TIME_ZONE]) - solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :] - + latitude = float(meta[Variables.LAT.col_name]) + longitude = float(meta[Variables.LON.col_name]) + time_zone = float(meta[Variables.TIME_ZONE.col_name]) + solpos = df.loc[df[Variables.APPARENT_ELEVATION.col_name] > 0, :] if var != "None": - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_name = mapping_dictionary[var][ColNames.NAME] - var_color = mapping_dictionary[var][ColNames.COLOR] + variable = VariableInfo.from_col_name(var) + var_unit = variable.get_unit(si_ip) + var_range = variable.get_range(si_ip) + var_name = variable.get_name() + var_color = variable.get_color() if global_local == "global": # Set Global values for Max and minimum range_z = var_range @@ -145,7 +158,7 @@ def polar_graph(df, meta, global_local, var, si_ip): ) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta - solpos = df.loc[df[ColNames.APPARENT_ELEVATION] > 0, :] + solpos = df.loc[df[Variables.APPARENT_ELEVATION.col_name] > 0, :] if var == "None": var_color = "orange" @@ -176,19 +189,20 @@ def polar_graph(df, meta, global_local, var, si_ip): if var == "None": fig.add_trace( go.Scatterpolar( - r=90 * np.cos(np.radians(90 - solpos[ColNames.APPARENT_ZENITH])), - theta=solpos[ColNames.AZIMUTH], + r=90 + * np.cos(np.radians(90 - solpos[Variables.APPARENT_ZENITH.col_name])), + theta=solpos[Variables.AZIMUTH.col_name], mode="markers", marker_color="orange", marker_size=marker_size, marker_line_width=0, customdata=np.stack( ( - solpos[ColNames.DAY], - solpos[ColNames.MONTH_NAMES], - solpos[ColNames.HOUR], - solpos[ColNames.ELEVATION], - solpos[ColNames.AZIMUTH], + solpos[Variables.DAY.col_name], + solpos[Variables.MONTH_NAMES.col_name], + solpos[Variables.HOUR.col_name], + solpos[Variables.ELEVATION.col_name], + solpos[Variables.AZIMUTH.col_name], ), axis=-1, ), @@ -206,8 +220,9 @@ def polar_graph(df, meta, global_local, var, si_ip): else: fig.add_trace( go.Scatterpolar( - r=90 * np.cos(np.radians(90 - solpos[ColNames.APPARENT_ZENITH])), - theta=solpos[ColNames.AZIMUTH], + r=90 + * np.cos(np.radians(90 - solpos[Variables.APPARENT_ZENITH.col_name])), + theta=solpos[Variables.AZIMUTH.col_name], mode="markers", marker=dict( color=solpos[var], @@ -220,11 +235,11 @@ def polar_graph(df, meta, global_local, var, si_ip): ), customdata=np.stack( ( - solpos[ColNames.DAY], - solpos[ColNames.MONTH_NAMES], - solpos[ColNames.HOUR], - solpos[ColNames.ELEVATION], - solpos[ColNames.AZIMUTH], + solpos[Variables.DAY.col_name], + solpos[Variables.MONTH_NAMES.col_name], + solpos[Variables.HOUR.col_name], + solpos[Variables.ELEVATION.col_name], + solpos[Variables.AZIMUTH.col_name], solpos[var], ), axis=-1, @@ -250,13 +265,13 @@ def polar_graph(df, meta, global_local, var, si_ip): for date in pd.to_datetime(["2019-03-21", "2019-06-21", "2019-12-21"]): times = pd.date_range( date, - date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), - freq=ColNames.FIVE_MINUTE, + date + pd.Timedelta(Variables.TWENTY_FOUR_HOUR.col_name), + freq=Variables.FIVE_MINUTE.col_name, tz=tz, ) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] + solpos = solpos.loc[solpos[Variables.APPARENT_ELEVATION.col_name] > 0, :] fig.add_trace( go.Scatterpolar( @@ -279,13 +294,13 @@ def polar_graph(df, meta, global_local, var, si_ip): for date in pd.to_datetime(["2019-01-21", "2019-02-21", "2019-4-21", "2019-5-21"]): times = pd.date_range( date, - date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), - freq=ColNames.FIVE_MINUTE, + date + pd.Timedelta(Variables.TWENTY_FOUR_HOUR.col_name), + freq=Variables.FIVE_MINUTE.col_name, tz=tz, ) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] + solpos = solpos.loc[solpos[Variables.APPARENT_ELEVATION.col_name] > 0, :] fig.add_trace( go.Scatterpolar( @@ -333,16 +348,16 @@ def polar_graph(df, meta, global_local, var, si_ip): def custom_cartesian_solar(df, meta, global_local, var, si_ip): """Return a graph of a latitude and longitude solar diagram.""" - latitude = float(meta[ColNames.LAT]) - longitude = float(meta[ColNames.LON]) - time_zone = float(meta[ColNames.TIME_ZONE]) + latitude = float(meta[Variables.LAT.col_name]) + longitude = float(meta[Variables.LON.col_name]) + time_zone = float(meta[Variables.TIME_ZONE.col_name]) tz = "UTC" - + variable = VariableInfo.from_col_name(var) if var != "None": - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_name = mapping_dictionary[var][ColNames.NAME] - var_color = mapping_dictionary[var][ColNames.COLOR] + var_unit = variable.get_unit(si_ip) + var_range = variable.get_range(si_ip) + var_name = variable.get_name() + var_color = variable.get_color() if global_local == "global": # Set Global values for Max and minimum range_z = var_range @@ -364,19 +379,19 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): if var == "None": fig.add_trace( go.Scatter( - y=df[ColNames.ELEVATION], - x=df[ColNames.AZIMUTH], + y=df[Variables.ELEVATION.col_name], + x=df[Variables.AZIMUTH.col_name], mode="markers", marker_color="orange", marker_size=marker_size, marker_line_width=0, customdata=np.stack( ( - df[ColNames.DAY], - df[ColNames.MONTH_NAMES], - df[ColNames.HOUR], - df[ColNames.ELEVATION], - df[ColNames.AZIMUTH], + df[Variables.DAY.col_name], + df[Variables.MONTH_NAMES.col_name], + df[Variables.HOUR.col_name], + df[Variables.ELEVATION.col_name], + df[Variables.AZIMUTH.col_name], ), axis=-1, ), @@ -394,8 +409,8 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): else: fig.add_trace( go.Scatter( - y=df[ColNames.ELEVATION], - x=df[ColNames.AZIMUTH], + y=df[Variables.ELEVATION.col_name], + x=df[Variables.AZIMUTH.col_name], mode="markers", marker=dict( color=df[var], @@ -408,11 +423,11 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): ), customdata=np.stack( ( - df[ColNames.DAY], - df[ColNames.MONTH_NAMES], - df[ColNames.HOUR], - df[ColNames.ELEVATION], - df[ColNames.AZIMUTH], + df[Variables.DAY.col_name], + df[Variables.MONTH_NAMES.col_name], + df[Variables.HOUR.col_name], + df[Variables.ELEVATION.col_name], + df[Variables.AZIMUTH.col_name], df[var], ), axis=-1, @@ -438,14 +453,14 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): for date in pd.to_datetime(["2019-03-21", "2019-06-21", "2019-12-21"]): times = pd.date_range( date, - date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), - freq=ColNames.FIVE_MINUTE, + date + pd.Timedelta(Variables.TWENTY_FOUR_HOUR.col_name), + freq=Variables.FIVE_MINUTE.col_name, tz=tz, ) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] + solpos = solpos.loc[solpos[Variables.APPARENT_ELEVATION.col_name] > 0, :] fig.add_trace( go.Scatter( @@ -467,14 +482,14 @@ def custom_cartesian_solar(df, meta, global_local, var, si_ip): for date in pd.to_datetime(["2019-01-21", "2019-02-21", "2019-4-21", "2019-5-21"]): times = pd.date_range( date, - date + pd.Timedelta(ColNames.TWENTY_FOUR_HOUR), - freq=ColNames.FIVE_MINUTE, + date + pd.Timedelta(Variables.TWENTY_FOUR_HOUR.col_name), + freq=Variables.FIVE_MINUTE.col_name, tz=tz, ) delta = timedelta(days=0, hours=time_zone - 1, minutes=0) times = times - delta solpos = solarposition.get_solarposition(times, latitude, longitude) - solpos = solpos.loc[solpos[ColNames.APPARENT_ELEVATION] > 0, :] + solpos = solpos.loc[solpos[Variables.APPARENT_ELEVATION.col_name] > 0, :] fig.add_trace( go.Scatter( diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 2e4ba5f9..ded1c783 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -16,10 +16,10 @@ from pythermalcomfort.models import solar_gain from pythermalcomfort.models import utci from pythermalcomfort.utilities import running_mean_outdoor_temperature - +from config import UnitSystem from pages.lib.global_scheme import month_lst from pages.lib.utils import code_timer -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables, VariableInfo @code_timer @@ -68,7 +68,7 @@ def get_location_info(lst, file_name): # from OneClimaBuilding files extract info about reference years try: - location_info[ColNames.PERIOD] = re.search( + location_info[Variables.PERIOD.col_name] = re.search( r'cord=[\'"]?([^\'" >]+);', lst[5] ).group(1) except AttributeError: @@ -87,7 +87,7 @@ def utci_calc( t_air_col: str, t_rad_col: str, wind_col: str, - rh_col: str = ColNames.RH, + rh_col: str = Variables.RH.col_name, ) -> pd.Series: """Call utci() using values from df columns.""" return utci(df[t_air_col], df[t_rad_col], df[wind_col], df[rh_col]) @@ -102,21 +102,25 @@ def add_utci_variants(df: pd.DataFrame) -> pd.DataFrame: - Sun_noWind : DBT + MRT + wind_speed_utci_0 """ recipes = { - ColNames.UTCI_NO_SUN_WIND: ( - ColNames.DBT, - ColNames.DBT, - ColNames.WIND_SPEED_UTCI, + Variables.UTCI_NO_SUN_WIND.col_name: ( + Variables.DBT.col_name, + Variables.DBT.col_name, + Variables.WIND_SPEED_UTCI.col_name, + ), + Variables.UTCI_NO_SUN_NO_WIND.col_name: ( + Variables.DBT.col_name, + Variables.DBT.col_name, + Variables.WIND_SPEED_UTCI_0.col_name, ), - ColNames.UTCI_NO_SUN_NO_WIND: ( - ColNames.DBT, - ColNames.DBT, - ColNames.WIND_SPEED_UTCI_0, + Variables.UTCI_SUN_WIND.col_name: ( + Variables.DBT.col_name, + Variables.MRT.col_name, + Variables.WIND_SPEED_UTCI.col_name, ), - ColNames.UTCI_SUN_WIND: (ColNames.DBT, ColNames.MRT, ColNames.WIND_SPEED_UTCI), - ColNames.UTCI_SUN_NO_WIND: ( - ColNames.DBT, - ColNames.MRT, - ColNames.WIND_SPEED_UTCI_0, + Variables.UTCI_SUN_NO_WIND.col_name: ( + Variables.DBT.col_name, + Variables.MRT.col_name, + Variables.WIND_SPEED_UTCI_0.col_name, ), } for out_col, (t_air, t_rad, wind) in recipes.items(): @@ -127,10 +131,10 @@ def add_utci_variants(df: pd.DataFrame) -> pd.DataFrame: def add_utci_categories(df: pd.DataFrame) -> pd.DataFrame: """Bin the four UTCI columns into categories.""" mapping = { - ColNames.UTCI_NO_SUN_WIND: ColNames.UTCI_NOSUN_WIND_CATEGORIES, - ColNames.UTCI_NO_SUN_NO_WIND: ColNames.UTCI_NOSUN_NOWIND_CATEGORIES, - ColNames.UTCI_SUN_WIND: ColNames.UTCI_SUN_WIND_CATEGORIES, - ColNames.UTCI_SUN_NO_WIND: ColNames.UTCI_SUN_NOWIND_CATEGORIES, + Variables.UTCI_NO_SUN_WIND.col_name: Variables.UTCI_NOSUN_WIND_CATEGORIES.col_name, + Variables.UTCI_NO_SUN_NO_WIND.col_name: Variables.UTCI_NOSUN_NOWIND_CATEGORIES.col_name, + Variables.UTCI_SUN_WIND.col_name: Variables.UTCI_SUN_WIND_CATEGORIES.col_name, + Variables.UTCI_SUN_NO_WIND.col_name: Variables.UTCI_SUN_NOWIND_CATEGORIES.col_name, } for src_col, dst_col in mapping.items(): df[dst_col] = pd.cut(x=df[src_col], bins=UTCI_BINS, labels=UTCI_LABELS) @@ -156,7 +160,7 @@ def create_df(lst, file_name): # from OneClimaBuilding files extract info about reference years try: - location_info[ColNames.PERIOD] = re.search( + location_info[Variables.PERIOD.col_name] = re.search( r'cord=[\'"]?([^\'" >]+);', lst[5] ).group(1) except AttributeError: @@ -174,35 +178,35 @@ def create_df(lst, file_name): del line[-1] col_names = [ - "year", - "month", - "day", - "hour", - "DBT", - "DPT", - "RH", - "p_atm", - "extr_hor_rad", - "hor_ir_rad", - "glob_hor_rad", - "dir_nor_rad", - "dif_hor_rad", - "glob_hor_ill", - "dir_nor_ill", - "dif_hor_ill", - "Zlumi", - "wind_dir", - ColNames.WIND_SPEED, - "tot_sky_cover", - "Oskycover", - "Vis", - "Cheight", - "PWobs", - "PWcodes", - "Pwater", - "AsolOptD", - "SnowD", - "DaySSnow", + Variables.YEAR.col_name, + Variables.MONTH.col_name, + Variables.DAY.col_name, + Variables.HOUR.col_name, + Variables.DBT.col_name, + Variables.DPT.col_name, + Variables.RH.col_name, + Variables.P_ATM.col_name, + Variables.EXTR_HOR_RAD.col_name, + Variables.HOR_IR_RAD.col_name, + Variables.GLOB_HOR_RAD.col_name, + Variables.DIR_NOR_RAD.col_name, + Variables.DIF_HOR_RAD.col_name, + Variables.GLOB_HOR_ILL.col_name, + Variables.DIR_NOR_ILL.col_name, + Variables.DIF_HOR_ILL.col_name, + Variables.ZLUMI.col_name, + Variables.WIND_DIR.col_name, + Variables.WIND_SPEED.col_name, + Variables.TOT_SKY_COVER.col_name, + Variables.OPAQUE_SKY_COVER.col_name, + Variables.VIS.col_name, + Variables.CLOUD_HEIGHT.col_name, + Variables.PRECIPITATION_OBSERVATION.col_name, + Variables.PRECIPITATION_CODES.col_name, + Variables.PRECIPITATION_WATER.col_name, + Variables.AEROSOL_OPTICAL_DEPTH.col_name, + Variables.SNOW_DEPTH.col_name, + Variables.DAILY_SNOW.col_name, ] # assign column names, if fewer cols are there than supposed assign 9999 to that col @@ -214,70 +218,88 @@ def create_df(lst, file_name): epw_df = pd.DataFrame(columns=col_names, data=lst) # from EnergyPlus files extract info about reference years - if not location_info[ColNames.PERIOD]: - years = epw_df[ColNames.YEAR].astype("int").unique() + if not location_info[Variables.PERIOD.col_name]: + years = epw_df[Variables.YEAR.col_name].astype("int").unique() if len(years) == 1: year_rounded_up = int(math.ceil(years[0] / 10.0)) * 10 - location_info[ColNames.PERIOD] = f"{year_rounded_up - 10}-{year_rounded_up}" + location_info[Variables.PERIOD.col_name] = ( + f"{year_rounded_up - 10}-{year_rounded_up}" + ) else: min_year = int(math.floor(min(years) / 10.0)) * 10 max_year = int(math.ceil(max(years) / 10.0)) * 10 - location_info[ColNames.PERIOD] = f"{min_year}-{max_year}" + location_info[Variables.PERIOD.col_name] = f"{min_year}-{max_year}" # Add fake_year - epw_df[ColNames.FAKE_YEAR] = ColNames.YEAR + epw_df[Variables.FAKE_YEAR.col_name] = Variables.YEAR.col_name # Add in month names month_look_up = {ix + 1: month for ix, month in enumerate(month_lst)} - epw_df[ColNames.MONTH_NAMES] = ( - epw_df[ColNames.MONTH].astype("int").map(month_look_up) + epw_df[Variables.MONTH_NAMES.col_name] = ( + epw_df[Variables.MONTH.col_name].astype("int").map(month_look_up) ) # Change to int type - epw_df[[ColNames.YEAR, ColNames.DAY, ColNames.MONTH, ColNames.HOUR]] = epw_df[ - [ColNames.YEAR, ColNames.DAY, ColNames.MONTH, ColNames.HOUR] + epw_df[ + [ + Variables.YEAR.col_name, + Variables.DAY.col_name, + Variables.MONTH.col_name, + Variables.HOUR.col_name, + ] + ] = epw_df[ + [ + Variables.YEAR.col_name, + Variables.DAY.col_name, + Variables.MONTH.col_name, + Variables.HOUR.col_name, + ] ].astype(int) # Add in DOY df_doy = ( - epw_df.groupby([ColNames.MONTH, ColNames.DAY])[ColNames.HOUR] + epw_df.groupby([Variables.MONTH.col_name, Variables.DAY.col_name])[ + Variables.HOUR.col_name + ] .count() .reset_index() ) - df_doy[ColNames.DOY] = df_doy.index + 1 + df_doy[Variables.DOY.col_name] = df_doy.index + 1 epw_df = pd.merge( epw_df, - df_doy[[ColNames.MONTH, ColNames.DAY, ColNames.DOY]], - on=[ColNames.MONTH, ColNames.DAY], + df_doy[ + [Variables.MONTH.col_name, Variables.DAY.col_name, Variables.DOY.col_name] + ], + on=[Variables.MONTH.col_name, Variables.DAY.col_name], how="left", ) change_to_float = [ - "DBT", - "DPT", - "RH", - "p_atm", - "extr_hor_rad", - "hor_ir_rad", - "glob_hor_rad", - "dir_nor_rad", - "dif_hor_rad", - "glob_hor_ill", - "dir_nor_ill", - "dif_hor_ill", - "Zlumi", - "wind_dir", - ColNames.WIND_SPEED, - "tot_sky_cover", - "Oskycover", - "Vis", - "Cheight", - "PWobs", - "PWcodes", - "Pwater", - "AsolOptD", - "SnowD", - "DaySSnow", + Variables.DBT.col_name, + Variables.DPT.col_name, + Variables.RH.col_name, + Variables.P_ATM.col_name, + Variables.EXTR_HOR_RAD.col_name, + Variables.HOR_IR_RAD.col_name, + Variables.GLOB_HOR_RAD.col_name, + Variables.DIR_NOR_RAD.col_name, + Variables.DIF_HOR_RAD.col_name, + Variables.GLOB_HOR_ILL.col_name, + Variables.DIR_NOR_ILL.col_name, + Variables.DIF_HOR_ILL.col_name, + Variables.ZLUMI.col_name, + Variables.WIND_DIR.col_name, + Variables.WIND_SPEED.col_name, + Variables.TOT_SKY_COVER.col_name, + Variables.OPAQUE_SKY_COVER.col_name, + Variables.VIS.col_name, + Variables.CLOUD_HEIGHT.col_name, + Variables.PRECIPITATION_OBSERVATION.col_name, + Variables.PRECIPITATION_CODES.col_name, + Variables.PRECIPITATION_WATER.col_name, + Variables.AEROSOL_OPTICAL_DEPTH.col_name, + Variables.SNOW_DEPTH.col_name, + Variables.DAILY_SNOW.col_name, ] epw_df[change_to_float] = epw_df[change_to_float].astype(float) @@ -285,24 +307,34 @@ def create_df(lst, file_name): times = pd.date_range( "2019-01-01 00:00:00", "2020-01-01", inclusive="left", freq="h", tz="UTC" ) - epw_df[ColNames.UTC_TIME] = pd.to_datetime(times) - delta = timedelta(days=0, hours=location_info[ColNames.TIME_ZONE] - 1, minutes=0) + epw_df[Variables.UTC_TIME.col_name] = pd.to_datetime(times) + delta = timedelta( + days=0, hours=location_info[Variables.TIME_ZONE.col_name] - 1, minutes=0 + ) times = times - delta - epw_df[ColNames.TIMES] = times + epw_df[Variables.TIMES.col_name] = times epw_df.set_index( - ColNames.TIMES, drop=False, append=False, inplace=True, verify_integrity=False + Variables.TIMES.col_name, + drop=False, + append=False, + inplace=True, + verify_integrity=False, ) # Add in solar position df solar_position = solarposition.get_solarposition( - times, location_info[ColNames.LAT], location_info[ColNames.LON] + times, + location_info[Variables.LAT.col_name], + location_info[Variables.LON.col_name], ) epw_df = pd.concat([epw_df, solar_position], axis=1) # Add in UTCI - sol_altitude = epw_df[ColNames.ELEVATION].mask(epw_df[ColNames.ELEVATION] <= 0, 0) + sol_altitude = epw_df[Variables.ELEVATION.col_name].mask( + epw_df[Variables.ELEVATION.col_name] <= 0, 0 + ) sharp = expand_to_hours(45) - sol_radiation_dir = epw_df[ColNames.DIR_NOR_RAD] + sol_radiation_dir = epw_df[Variables.DIR_NOR_RAD.col_name] sol_transmittance = expand_to_hours(1) # CHECK VALUE f_svv = expand_to_hours(1) # CHECK VALUE f_bes = expand_to_hours(1) # CHECK VALUE @@ -322,44 +354,52 @@ def create_df(lst, file_name): floor_reflectance, ) mrt_df = pd.DataFrame.from_records(mrt) - mrt_df[ColNames.DELTA_MRT] = mrt_df[ColNames.DELTA_MRT].mask( - mrt_df[ColNames.DELTA_MRT] >= 70, 70 + mrt_df[Variables.DELTA_MRT.col_name] = mrt_df[Variables.DELTA_MRT.col_name].mask( + mrt_df[Variables.DELTA_MRT.col_name] >= 70, 70 ) mrt_df = mrt_df.set_index(epw_df.times) epw_df = epw_df.join(mrt_df) - epw_df[ColNames.MRT] = epw_df[ColNames.DELTA_MRT] + epw_df[ColNames.DBT] - epw_df[ColNames.WIND_SPEED_UTCI] = epw_df[ColNames.WIND_SPEED] - epw_df[ColNames.WIND_SPEED_UTCI] = epw_df[ColNames.WIND_SPEED_UTCI].mask( - epw_df[ColNames.WIND_SPEED_UTCI] >= 17, 16.9 - ) - epw_df[ColNames.WIND_SPEED_UTCI] = epw_df[ColNames.WIND_SPEED_UTCI].mask( - epw_df[ColNames.WIND_SPEED_UTCI] <= 0.5, 0.6 - ) - epw_df[ColNames.WIND_SPEED_UTCI_0] = epw_df[ColNames.WIND_SPEED_UTCI].mask( - epw_df[ColNames.WIND_SPEED_UTCI] >= 0, 0.5 + epw_df[Variables.MRT.col_name] = ( + epw_df[Variables.DELTA_MRT.col_name] + epw_df[Variables.DBT.col_name] ) + epw_df[Variables.WIND_SPEED_UTCI.col_name] = epw_df[Variables.WIND_SPEED.col_name] + epw_df[Variables.WIND_SPEED_UTCI.col_name] = epw_df[ + Variables.WIND_SPEED_UTCI.col_name + ].mask(epw_df[Variables.WIND_SPEED_UTCI.col_name] >= 17, 16.9) + epw_df[Variables.WIND_SPEED_UTCI.col_name] = epw_df[ + Variables.WIND_SPEED_UTCI.col_name + ].mask(epw_df[Variables.WIND_SPEED_UTCI.col_name] <= 0.5, 0.6) + epw_df[Variables.WIND_SPEED_UTCI_0.col_name] = epw_df[ + Variables.WIND_SPEED_UTCI.col_name + ].mask(epw_df[Variables.WIND_SPEED_UTCI.col_name] >= 0, 0.5) epw_df = add_utci_variants(epw_df) epw_df = add_utci_categories(epw_df) # Add psy values - ta_rh = np.vectorize(psy.psy_ta_rh)(epw_df[ColNames.DBT], epw_df[ColNames.RH]) + ta_rh = np.vectorize(psy.psy_ta_rh)( + epw_df[Variables.DBT.col_name], epw_df[Variables.RH.col_name] + ) psy_df = pd.DataFrame.from_records(ta_rh) psy_df = psy_df.set_index(epw_df.times) epw_df = epw_df.join(psy_df) # calculate adaptive data - dbt_day_ave = epw_df.groupby([ColNames.DOY])[ColNames.DBT].mean().to_list() + dbt_day_ave = ( + epw_df.groupby([Variables.DOY.col_name])[Variables.DBT.col_name] + .mean() + .to_list() + ) n = 7 - epw_df[ColNames.ADAPTIVE_COMFORT] = np.nan - epw_df[ColNames.ADAPTIVE_CMF_80_LOW] = np.nan - epw_df[ColNames.ADAPTIVE_CMF_80_UP] = np.nan - epw_df[ColNames.ADAPTIVE_CMF_90_LOW] = np.nan - epw_df[ColNames.ADAPTIVE_CMF_90_UP] = np.nan - epw_df[ColNames.ADAPTIVE_CMF_RMT] = np.nan + epw_df[Variables.ADAPTIVE_COMFORT.col_name] = np.nan + epw_df[Variables.ADAPTIVE_CMF_80_LOW.col_name] = np.nan + epw_df[Variables.ADAPTIVE_CMF_80_UP.col_name] = np.nan + epw_df[Variables.ADAPTIVE_CMF_90_LOW.col_name] = np.nan + epw_df[Variables.ADAPTIVE_CMF_90_UP.col_name] = np.nan + epw_df[Variables.ADAPTIVE_CMF_RMT.col_name] = np.nan for day in epw_df.DOY.unique(): i = day - 1 if i < n: @@ -380,19 +420,21 @@ def create_df(lst, file_name): v=0.5, limit_inputs=False, ) - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_RMT] = rmt - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_COMFORT] = r[ColNames.TMP_CMF] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_LOW] = r[ - ColNames.TMP_CMF_80_LOW + epw_df.loc[epw_df.DOY == day, Variables.ADAPTIVE_CMF_RMT.col_name] = rmt + epw_df.loc[epw_df.DOY == day, Variables.ADAPTIVE_COMFORT.col_name] = r[ + Variables.TMP_CMF.col_name ] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_80_UP] = r[ - ColNames.TMP_CMF_80_UP + epw_df.loc[epw_df.DOY == day, Variables.ADAPTIVE_CMF_80_LOW.col_name] = r[ + Variables.TMP_CMF_80_LOW.col_name ] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_LOW] = r[ - ColNames.TMP_CMF_90_LOW + epw_df.loc[epw_df.DOY == day, Variables.ADAPTIVE_CMF_80_UP.col_name] = r[ + Variables.TMP_CMF_80_UP.col_name ] - epw_df.loc[epw_df.DOY == day, ColNames.ADAPTIVE_CMF_90_UP] = r[ - ColNames.TMP_CMF_90_UP + epw_df.loc[epw_df.DOY == day, Variables.ADAPTIVE_CMF_90_LOW.col_name] = r[ + Variables.TMP_CMF_90_LOW.col_name + ] + epw_df.loc[epw_df.DOY == day, Variables.ADAPTIVE_CMF_90_UP.col_name] = r[ + Variables.TMP_CMF_90_UP.col_name ] return epw_df, location_info @@ -408,47 +450,55 @@ def convert_SI_to_IP(df: pd.DataFrame, name: str) -> None: return match name: case ( - ColNames.DBT - | ColNames.DPT - | ColNames.T_WB - | ColNames.T_DP - | ColNames.UTCI_SUN_WIND - | ColNames.UTCI_NO_SUN_WIND - | ColNames.UTCI_SUN_NO_WIND - | ColNames.UTCI_NO_SUN_NO_WIND - | ColNames.ADAPTIVE_COMFORT - | ColNames.ADAPTIVE_CMF_80_LOW - | ColNames.ADAPTIVE_CMF_80_UP - | ColNames.ADAPTIVE_CMF_90_LOW - | ColNames.ADAPTIVE_CMF_90_UP + Variables.DBT.col_name + | Variables.DPT.col_name + | Variables.T_WB.col_name + | Variables.T_DP.col_name + | Variables.UTCI_SUN_WIND.col_name + | Variables.UTCI_NO_SUN_WIND.col_name + | Variables.UTCI_SUN_NO_WIND.col_name + | Variables.UTCI_NO_SUN_NO_WIND.col_name + | Variables.ADAPTIVE_COMFORT.col_name + | Variables.ADAPTIVE_CMF_80_LOW.col_name + | Variables.ADAPTIVE_CMF_80_UP.col_name + | Variables.ADAPTIVE_CMF_90_LOW.col_name + | Variables.ADAPTIVE_CMF_90_UP.col_name ): df[name] = df[name] * 1.8 + 32 - case ColNames.P_ATM | ColNames.P_VAP | ColNames.P_SAT: + case ( + Variables.P_ATM.col_name + | Variables.P_VAP.col_name + | Variables.P_SAT.col_name + ): df[name] = df[name] * 0.000145038 case ( - ColNames.EXTR_HOR_RAD - | ColNames.HOR_IR_RAD - | ColNames.GLOB_HOR_RAD - | ColNames.DIR_NOR_RAD - | ColNames.DIF_HOR_RAD + Variables.EXTR_HOR_RAD.col_name + | Variables.HOR_IR_RAD.col_name + | Variables.GLOB_HOR_RAD.col_name + | Variables.DIR_NOR_RAD.col_name + | Variables.DIF_HOR_RAD.col_name ): df[name] = df[name] * 0.3169983306 - case ColNames.GLOB_HOR_ILL | ColNames.DIR_NOR_ILL | ColNames.DIF_HOR_ILL: + case ( + Variables.GLOB_HOR_ILL.col_name + | Variables.DIR_NOR_ILL.col_name + | Variables.DIF_HOR_ILL.col_name + ): df[name] = df[name] * 0.0929 - case ColNames.ZLUMI: + case Variables.ZLUMI.col_name: df[name] = df[name] * 0.0929 - case ColNames.WIND_SPEED: + case Variables.WIND_SPEED.col_name: df[name] = df[name] * 196.85039370078738 - case ColNames.VIS: + case Variables.VIS.col_name: df[name] = df[name] * 0.6215 - case ColNames.EH: + case Variables.EH.col_name: df[name] = df[name] * 0.000429923 case _: @@ -456,6 +506,23 @@ def convert_SI_to_IP(df: pd.DataFrame, name: str) -> None: pass +def convert_df_units(df: pd.DataFrame, unit_system: str) -> pd.DataFrame: + """Convert DataFrame columns to the specified unit system.""" + if unit_system != UnitSystem.IP: + return df # Currently we only support SI → IP + + df_converted = df.copy() + + for attr in dir(Variables): + if attr.isupper(): + var = getattr(Variables, attr) + if isinstance(var, VariableInfo): + col = var.col_name + convert_SI_to_IP(df_converted, col) + + return df_converted + + def convert_data(df, mapping_json): mapping_dict = json.loads(mapping_json) for key in mapping_dict: diff --git a/pages/lib/global_column_names.py b/pages/lib/global_column_names.py deleted file mode 100644 index 7980b900..00000000 --- a/pages/lib/global_column_names.py +++ /dev/null @@ -1,120 +0,0 @@ -class ColNames: - # ==================== Time related column ==================== - YEAR = "year" # year - PERIOD = "period" # period - MONTH = "month" # month - DAY = "day" # day - HOUR = "hour" # hour - MINUTE = "minute" # minute - - # ==================== Location related column ==================== - LAT = "lat" # Latitude - LON = "lon" # Longitude - CITY = "city" # City - COUNTRY = "country" # Country - TIME_ZONE = "time_zone" # Time Zone - - # ==================== Meteorological data column ==================== - DBT = "DBT" # Dry Bulb Temperature - DPT = "DPT" # Dew Point Temperature - RH = "RH" # Relative Humidity - HI_RH = "hiRH" # High Relative Humidity - LO_RH = "loRH" # Low Relative Humidity - HR = "hr" # Absolute Humidity - EH = "h" # Enthalpy - P_ATM = "p_atm" # Atmospheric Pressure - P_VAP = "p_vap" # Vapor partial Pressure - P_SAT = "p_sat" # Saturation Pressure - T_WB = "t_wb" # Wet Bulb Temperature - T_DP = "t_dp" # Dew Point Temperature - - # ==================== Radiation-related column ==================== - EXTR_HOR_RAD = "extr_hor_rad" # Extraterrestrial Horizontal Radiation - HOR_IR_RAD = "hor_ir_rad" # Horizontal Infrared Radiation - GLOB_HOR_RAD = "glob_hor_rad" # Global Horizontal Radiation - DIR_NOR_RAD = "dir_nor_rad" # Direct Normal Radiation - DIF_HOR_RAD = "dif_hor_rad" # Diffuse Horizontal Radiation - - # ==================== Lighting-related columns ==================== - GLOB_HOR_ILL = "glob_hor_ill" # Global Horizontal Illuminance - DIR_NOR_ILL = "dir_nor_ill" # Direct Normal Illuminance - DIF_HOR_ILL = "dif_hor_ill" # Diffuse Horizontal Illuminance - - # ==================== Other columns ==================== - ZLUMI = "Zlumi" # Luminance - WIND_DIR = "wind_dir" # Wind Direction - WIND_SPEED = "wind_speed" # Wind Speed - WIND_SPEED_UTCI = "wind_speed_utci" # Wind Speed Utci - WIND_SPEED_UTCI_0 = "wind_speed_utci_0" # Wind Speed Utci 0 - TOT_SKY_COVER = "tot_sky_cover" # Total Sky Cover - OSKYCOVER = "Oskycover" # Opaque Sky Cover - VIS = "Vis" # Visibility - CHEIGHT = "Cheight" # Cloud Height - # PWobs = "PWobs" # Precipitation Observation - # PWcodes = "PWcodes" # Precipitation Codes - # Pwater = "Pwater" # Precipitation Water - # AsolOptD = "AsolOptD" # Aerosol Optical Depth - # SnowD = "SnowD" # Snow Depth - # DaySSnow = "DaySSnow" # Daily Snow - ELEVATION = "elevation" # Elevation - EQUATION_OF_TIME = "equation_of_time" # Equation of time - APPARENT_ELEVATION = "apparent_elevation" # Apparent Elevation - APPARENT_ZENITH = "apparent_zenith" # Apparent Zenith - AZIMUTH = "azimuth" # Azimuth - ZENITH = "zenith" # Zenith - MRT = "MRT" - DELTA_MRT = "delta_mrt" - UTCI_SUN_WIND = "utci_Sun_Wind" # Utci Sun Wind - UTCI_SUN_NO_WIND = "utci_Sun_noWind" # Utci Sun no Wind - UTCI_NO_SUN_WIND = "utci_noSun_Wind" # Utci no Sun Wind - UTCI_NO_SUN_NO_WIND = "utci_noSun_noWind" # Utci no Sun no Wind - UTCI_SUN_WIND_CATEGORIES = "utci_Sun_Wind_categories" # Utci Sun Wind Categories - UTCI_SUN_NOWIND_CATEGORIES = ( - "utci_Sun_noWind_categories" # Utci Sun no Wind Categories - ) - UTCI_NOSUN_WIND_CATEGORIES = ( - "utci_noSun_Wind_categories" # Utci no Sun Wind Categories - ) - UTCI_NOSUN_NOWIND_CATEGORIES = ( - "utci_noSun_noWind_categories" # Utci no Sun no Wind Categories - ) - ADAPTIVE_COMFORT = "adaptive_comfort" # Adaptive comfort - ADAPTIVE_CMF_80_LOW = "adaptive_cmf_80_low" # Adaptive comfort 80 low - ADAPTIVE_CMF_80_UP = "adaptive_cmf_80_up" # Adaptive comfort 80 up - ADAPTIVE_CMF_90_LOW = "adaptive_cmf_90_low" # Adaptive comfort 90 low - ADAPTIVE_CMF_90_UP = "adaptive_cmf_90_up" # Adaptive comfort 90 up - ADAPTIVE_CMF_RMT = "adaptive_cmf_rmt" # Adaptive comfort rmt - NV_ALLOWED = "nv_allowed" - TMP_CMF = "tmp_cmf" - TMP_CMF_80_LOW = "tmp_cmf_80_low" - TMP_CMF_80_UP = "tmp_cmf_80_up" - TMP_CMF_90_LOW = "tmp_cmf_90_low" - TMP_CMF_90_UP = "tmp_cmf_90_up" - CONVERSION_FUNCTION = "conversion_function" - - # ==================== Calculation column ==================== - FAKE_YEAR = "fake_year" # Fake Year - MONTH_NAMES = "month_names" # Month names - UTC_TIME = "UTC_time" # UTC Time - DOY = "DOY" # Day of Year - - COLOR = "color" - NAME = "name" - RANGE = "range" - UNIT = "unit" - TWENTY_FOUR_HOUR = "24h" - FIVE_MINUTE = "5min" - TIMES = "times" - - PATH = "path" - FILE_NAME = "filename" - WIND_DIR_BINS = "WindDir_bins" - WIND_SPD_BINS = "WindSpd_bins" - - TO_IMAGE_BUTTON_OPTIONS = "toImageButtonOptions" - INVERT = "invert" - FEATURES = "features" - GEOMETRY_COORDINATES = "geometry.coordinates" - PROP_ID = "prop_id" - SITE_ELEVATION = "site_elevation" - NONE = "None" diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index 0793276c..36fd2d00 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,7 +1,6 @@ import plotly.io as pio -from pages.lib.global_column_names import ColNames -from config import UnitSystem +from pages.lib.global_variables import Variables, VariableInfo # Colors Dictionary blue_red_yellow = ["#00b3ff", "#000082", "#ff0000", "#ffff00"] @@ -111,791 +110,105 @@ temperature_unit = "\u00b0C" thermal_stress_label = "Thermal stress" -mapping_dictionary = { - ColNames.NONE: {ColNames.NAME: "None"}, - ColNames.DOY: { - ColNames.NAME: "Day of the year", - ColNames.UNIT: "days", - ColNames.RANGE: [0, 365], - }, - ColNames.DAY: {ColNames.NAME: "day", ColNames.RANGE: [1, 31]}, - ColNames.MONTH: { - ColNames.NAME: "months", - ColNames.UNIT: "months", - ColNames.RANGE: [1, 12], - }, - ColNames.HOUR: { - ColNames.NAME: "Hour", - ColNames.COLOR: [ - "#000000", - "#355e7e", - "#6b5c7b", - "#c06c84", - "#f8b195", - "#c92a42", - "#c92a42", - "#c92a42", - "#000000", - ], - ColNames.UNIT: "h", - ColNames.RANGE: [1, 24], - }, - ColNames.DBT: { - ColNames.NAME: "Dry bulb temperature", - ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - UnitSystem.SI: { - ColNames.UNIT: "°C", - ColNames.RANGE: [-40, 50], - }, - UnitSystem.IP: { - ColNames.UNIT: "°F", - ColNames.RANGE: [-40, 122], - }, - }, - ColNames.DPT: { - ColNames.NAME: "Dew point temperature", - ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - UnitSystem.SI: { - ColNames.UNIT: "°C", - ColNames.RANGE: [-50, 35], - }, - UnitSystem.IP: { - ColNames.UNIT: "°F", - ColNames.RANGE: [-58, 95], - }, - }, - ColNames.RH: { - ColNames.NAME: "Relative humidity", - ColNames.COLOR: ["#ffe600", "#00c8ff", "#0000ff"], - UnitSystem.SI: { - ColNames.UNIT: "%", - ColNames.RANGE: [0, 100], - }, - UnitSystem.IP: { - ColNames.UNIT: "%", - ColNames.RANGE: [0, 100], - }, - }, - ColNames.P_ATM: { - ColNames.NAME: "Atmospheric pressure", - ColNames.COLOR: [ - "#ffffff", - "#b2f2ff", - "#33ddff", - "#00aaff", - "#0055ff", - "#0000ff", - "#aa00ff", - "#ff00ff", - "#cc0000", - "#ffaa00", - ], - UnitSystem.SI: { - ColNames.UNIT: "Pa", - ColNames.RANGE: [95000, 105000], - }, - UnitSystem.IP: { - ColNames.UNIT: "Psi", - ColNames.RANGE: [95000 * 0.000145038, 1050000.000145038], - }, - }, - ColNames.EXTR_HOR_RAD: { - ColNames.NAME: "Extraterrestrial horizontal irradiation", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "Wh/m2", - ColNames.RANGE: [0, 1200], - }, - UnitSystem.IP: { - ColNames.UNIT: "Btu/ft2", - ColNames.RANGE: [0, 1200 * 0.3169983306], - }, - }, - ColNames.HOR_IR_RAD: { - ColNames.NAME: "Horizontal infrared radiation", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "Wh/m2", - ColNames.RANGE: [0, 500], - }, - UnitSystem.IP: { - ColNames.UNIT: "Btu/ft2", - ColNames.RANGE: [0, 500 * 0.3169983306], - }, - }, - ColNames.GLOB_HOR_RAD: { - ColNames.NAME: "Global horizontal radiation", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "Wh/m2", - ColNames.RANGE: [0, 1200], - }, - UnitSystem.IP: { - ColNames.UNIT: "Btu/ft2", - ColNames.RANGE: [0, 1200 * 0.3169983306], - }, - }, - ColNames.DIR_NOR_RAD: { - ColNames.NAME: "Direct normal radiation", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "Wh/m2", - ColNames.RANGE: [0, 1200], - }, - UnitSystem.IP: { - ColNames.UNIT: "Btu/ft2", - ColNames.RANGE: [0, 1200 * 0.3169983306], - }, - }, - ColNames.DIF_HOR_RAD: { - ColNames.NAME: "Diffuse horizontal radiation", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "Wh/m2", - ColNames.RANGE: [0, 1200], - }, - UnitSystem.IP: { - ColNames.UNIT: "Btu/ft2", - ColNames.RANGE: [0, 1200 * 0.3169983306], - }, - }, - ColNames.GLOB_HOR_ILL: { - ColNames.NAME: "Global horizontal illuminance", - ColNames.COLOR: ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], - UnitSystem.SI: { - ColNames.UNIT: "lux", - ColNames.RANGE: [0, 120000], - }, - UnitSystem.IP: { - ColNames.UNIT: "fc", - ColNames.RANGE: [0, 120000 * 0.0929], - }, - }, - ColNames.DIR_NOR_ILL: { - ColNames.NAME: "Direct normal illuminance", - ColNames.COLOR: ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], - UnitSystem.SI: { - ColNames.UNIT: "lux", - ColNames.RANGE: [0, 120000], - }, - UnitSystem.IP: { - ColNames.UNIT: "fc", - ColNames.RANGE: [0, 120000 * 0.0929], - }, - }, - ColNames.DIF_HOR_ILL: { - ColNames.NAME: "Diffuse horizontal illuminance", - ColNames.COLOR: ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], - UnitSystem.SI: { - ColNames.UNIT: "lux", - ColNames.RANGE: [0, 120000], - }, - UnitSystem.IP: { - ColNames.UNIT: "fc", - ColNames.RANGE: [0, 120000 * 0.0929], - }, - }, - ColNames.ZLUMI: { - ColNames.NAME: "Zenith luminance", - ColNames.COLOR: [ - "#730a8c", - "#0d0db3", - "#0f85be", - "#0f85be", - "#b11421", - "#fdf130", - ], - UnitSystem.SI: { - ColNames.UNIT: "cd/m2", - ColNames.RANGE: [0, 60000], - }, - UnitSystem.IP: { - ColNames.UNIT: "cd/ft2", - ColNames.RANGE: [0, 60000 * 0.0929], - }, - }, - ColNames.WIND_DIR: { - ColNames.NAME: "Wind direction", - ColNames.COLOR: ["#0072dd", "#00c420", "#eded00", "#be00d5", "#0072dd"], - UnitSystem.SI: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [0, 360], - }, - UnitSystem.IP: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [0, 360], - }, - }, - ColNames.WIND_SPEED: { - ColNames.NAME: "Wind speed", - ColNames.COLOR: [ - "#D3D3D3", - "#b2f2ff", - "#33ddff", - "#00aaff", - "#0055ff", - "#0000ff", - "#aa00ff", - "#ff00ff", - "#cc0000", - "#ffaa00", - ], - UnitSystem.SI: { - ColNames.UNIT: "m/s", - ColNames.RANGE: [0, 20], - }, - UnitSystem.IP: { - ColNames.UNIT: "fpm", - ColNames.RANGE: [0, 20 * 196.85039370078738], - }, - }, - ColNames.TOT_SKY_COVER: { - ColNames.NAME: "Total sky cover", - ColNames.COLOR: cloud_colors, - UnitSystem.SI: { - ColNames.UNIT: "tenths", - ColNames.RANGE: [0, 10], - }, - UnitSystem.IP: { - ColNames.UNIT: "tenths", - ColNames.RANGE: [0, 10], - }, - }, - ColNames.OSKYCOVER: { - ColNames.NAME: "Opaque sky cover", - ColNames.COLOR: cloud_colors, - UnitSystem.SI: { - ColNames.UNIT: "tenths", - ColNames.RANGE: [0, 10], - }, - UnitSystem.IP: { - ColNames.UNIT: "tenths", - ColNames.RANGE: [0, 10], - }, - }, - ColNames.VIS: { - ColNames.NAME: "Visibility", - ColNames.COLOR: cloud_colors, - UnitSystem.SI: { - ColNames.UNIT: "km", - ColNames.RANGE: [0, 100], - }, - UnitSystem.IP: { - ColNames.UNIT: "miles", - ColNames.RANGE: [0, 100 * 0.6215], - }, - }, - ColNames.APPARENT_ZENITH: { - ColNames.NAME: "Apparent zenith", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [0, 180], - }, - UnitSystem.IP: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [0, 180], - }, - }, - ColNames.ZENITH: { - ColNames.NAME: "Zenith", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [0, 180], - }, - UnitSystem.IP: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [0, 180], - }, - }, - ColNames.APPARENT_ELEVATION: { - ColNames.NAME: "Apparent elevation", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [-90, 90], - }, - UnitSystem.IP: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [-90, 90], - }, - }, - ColNames.ELEVATION: { - ColNames.NAME: "Elevation", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [-90, 90], - }, - UnitSystem.IP: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [-90, 90], - }, - }, - ColNames.AZIMUTH: { - ColNames.NAME: "Azimuth", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [0, 360], - }, - UnitSystem.IP: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [0, 360], - }, - }, - ColNames.EQUATION_OF_TIME: { - ColNames.NAME: "Equation of time", - ColNames.COLOR: [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - UnitSystem.SI: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [-20, 20], - }, - UnitSystem.IP: { - ColNames.UNIT: "°deg", - ColNames.RANGE: [-20, 20], - }, - }, - ColNames.UTCI_SUN_WIND: { - ColNames.NAME: "UTCI: Sun & Wind", - ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - UnitSystem.SI: { - ColNames.UNIT: "°C", - ColNames.RANGE: [-70, 70], - }, - UnitSystem.IP: { - ColNames.UNIT: "°F", - ColNames.RANGE: [-94, 158], - }, - }, - ColNames.UTCI_NO_SUN_WIND: { - ColNames.NAME: "UTCI: no Sun & Wind", - ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - UnitSystem.SI: { - ColNames.UNIT: "°C", - ColNames.RANGE: [-70, 70], - }, - UnitSystem.IP: { - ColNames.UNIT: "°F", - ColNames.RANGE: [-94, 158], - }, - }, - ColNames.UTCI_SUN_NO_WIND: { - ColNames.NAME: "UTCI: Sun & no Wind", - ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - UnitSystem.SI: { - ColNames.UNIT: "°C", - ColNames.RANGE: [-70, 70], - }, - UnitSystem.IP: { - ColNames.UNIT: "°F", - ColNames.RANGE: [-94, 158], - }, - }, - ColNames.UTCI_NO_SUN_NO_WIND: { - ColNames.NAME: "UTCI: no Sun & no Wind", - ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - UnitSystem.SI: { - ColNames.UNIT: "°C", - ColNames.RANGE: [-70, 70], - }, - UnitSystem.IP: { - ColNames.UNIT: "°F", - ColNames.RANGE: [-94, 158], - }, - }, - ColNames.UTCI_SUN_WIND_CATEGORIES: { - ColNames.NAME: "UTCI: Sun & Wind : categories", - ColNames.COLOR: [ - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.1665, "#38429B"], - [0.1665, "#4253A4"], - [0.2775, "#4253A4"], - [0.2775, "#4B62AD"], - [0.3885, "#4B62AD"], - [0.3885, "#68B8E7"], - [0.4995, "#68B8E7"], - [0.4995, "#53B848"], - [0.6105, "#53B848"], - [0.6105, "#EE8522"], - [0.7215, "#EE8522"], - [0.7215, "#EA2C24"], - [0.8325, "#EA2C24"], - [0.8325, "#B12224"], - [0.9435, "#B12224"], - [0.9435, "#751613"], - [1.0, "#751613"], - ], - UnitSystem.SI: { - ColNames.UNIT: thermal_stress_label, - ColNames.RANGE: [-5, 4], - }, - UnitSystem.IP: { - ColNames.UNIT: thermal_stress_label, - ColNames.RANGE: [-5, 4], - }, - }, - ColNames.UTCI_NOSUN_WIND_CATEGORIES: { - ColNames.NAME: "UTCI: no Sun & Wind : categories", - ColNames.COLOR: [ - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.1665, "#38429B"], - [0.1665, "#4253A4"], - [0.2775, "#4253A4"], - [0.2775, "#4B62AD"], - [0.3885, "#4B62AD"], - [0.3885, "#68B8E7"], - [0.4995, "#68B8E7"], - [0.4995, "#53B848"], - [0.6105, "#53B848"], - [0.6105, "#EE8522"], - [0.7215, "#EE8522"], - [0.7215, "#EA2C24"], - [0.8325, "#EA2C24"], - [0.8325, "#B12224"], - [0.9435, "#B12224"], - [0.9435, "#751613"], - [1.0, "#751613"], - ], - UnitSystem.SI: { - ColNames.UNIT: thermal_stress_label, - ColNames.RANGE: [-5, 4], - }, - UnitSystem.IP: { - ColNames.UNIT: thermal_stress_label, - ColNames.RANGE: [-5, 4], - }, - }, - ColNames.UTCI_SUN_NOWIND_CATEGORIES: { - ColNames.NAME: "UTCI: Sun & no Wind : categories", - ColNames.COLOR: [ - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.1665, "#38429B"], - [0.1665, "#4253A4"], - [0.2775, "#4253A4"], - [0.2775, "#4B62AD"], - [0.3885, "#4B62AD"], - [0.3885, "#68B8E7"], - [0.4995, "#68B8E7"], - [0.4995, "#53B848"], - [0.6105, "#53B848"], - [0.6105, "#EE8522"], - [0.7215, "#EE8522"], - [0.7215, "#EA2C24"], - [0.8325, "#EA2C24"], - [0.8325, "#B12224"], - [0.9435, "#B12224"], - [0.9435, "#751613"], - [1.0, "#751613"], - ], - UnitSystem.SI: { - ColNames.UNIT: thermal_stress_label, - ColNames.RANGE: [-5, 4], - }, - UnitSystem.IP: { - ColNames.UNIT: thermal_stress_label, - ColNames.RANGE: [-5, 4], - }, - }, - ColNames.UTCI_NOSUN_NOWIND_CATEGORIES: { - ColNames.NAME: "UTCI: no Sun & no Wind : categories", - ColNames.COLOR: [ - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.1665, "#38429B"], - [0.1665, "#4253A4"], - [0.2775, "#4253A4"], - [0.2775, "#4B62AD"], - [0.3885, "#4B62AD"], - [0.3885, "#68B8E7"], - [0.4995, "#68B8E7"], - [0.4995, "#53B848"], - [0.6105, "#53B848"], - [0.6105, "#EE8522"], - [0.7215, "#EE8522"], - [0.7215, "#EA2C24"], - [0.8325, "#EA2C24"], - [0.8325, "#B12224"], - [0.9435, "#B12224"], - [0.9435, "#751613"], - [1.0, "#751613"], - ], - UnitSystem.SI: { - ColNames.UNIT: thermal_stress_label, - ColNames.RANGE: [-5, 4], - }, - UnitSystem.IP: { - ColNames.UNIT: thermal_stress_label, - ColNames.RANGE: [-5, 4], - }, - }, - ColNames.P_VAP: { - ColNames.NAME: "Vapor partial pressure", - ColNames.COLOR: ["#ffe600", "#00c8ff", "#0000ff"], - UnitSystem.SI: { - ColNames.UNIT: "Pa", - ColNames.RANGE: [0, 5000], - }, - UnitSystem.IP: { - ColNames.UNIT: "Psi", - ColNames.RANGE: [0, 5000 * 0.000145038], - }, - }, - ColNames.P_SAT: { - ColNames.NAME: "Saturation pressure", - UnitSystem.SI: { - ColNames.UNIT: "Pa", - ColNames.RANGE: [0, 5000], - }, - UnitSystem.IP: { - ColNames.UNIT: "Psi", - ColNames.RANGE: [0, 5000 * 0.000145038], - }, - }, - ColNames.HR: { - ColNames.NAME: "Absolute humidity", - ColNames.COLOR: ["#ffe600", "#00c8ff", "#0000ff"], - UnitSystem.SI: { - ColNames.UNIT: "g water/kg dry air", - ColNames.RANGE: [0, 0.03 * 1000], - }, - UnitSystem.IP: { - ColNames.UNIT: "lb water/klb dry air", - ColNames.RANGE: [0, 0.03 * 1000], - }, - }, - ColNames.T_WB: { - ColNames.NAME: "Wet bulb temperature", - ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - UnitSystem.SI: { - ColNames.UNIT: "°C", - ColNames.RANGE: [-40, 50], - }, - UnitSystem.IP: { - ColNames.UNIT: "°F", - ColNames.RANGE: [-40, 122], - }, - }, - ColNames.T_DP: { - ColNames.NAME: "Dew point temperature", - ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - UnitSystem.SI: { - ColNames.UNIT: "°C", - ColNames.RANGE: [-40, 50], - }, - UnitSystem.IP: { - ColNames.UNIT: "°F", - ColNames.RANGE: [-40, 122], - }, - }, - ColNames.EH: { - ColNames.NAME: "Enthalpy", - ColNames.COLOR: ["#00b3ff", "#000082", "#ff0000", "#ffff00"], - UnitSystem.SI: { - ColNames.UNIT: "J/kg dry air", - ColNames.RANGE: [0, 110000], - }, - UnitSystem.IP: { - ColNames.UNIT: "Btu/lb dry air", - ColNames.RANGE: [0, 110000 * 0.000429923], - }, - }, -} - # Dropdown Names variables_sun_cloud_tab_dropdown = [ - ColNames.NONE, - ColNames.T_WB, - ColNames.DPT, - ColNames.DBT, - ColNames.RH, - ColNames.P_VAP, - ColNames.HR, - ColNames.EXTR_HOR_RAD, - ColNames.HOR_IR_RAD, - ColNames.GLOB_HOR_RAD, - ColNames.DIR_NOR_RAD, - ColNames.DIF_HOR_RAD, - ColNames.GLOB_HOR_ILL, - ColNames.DIR_NOR_ILL, - ColNames.DIF_HOR_ILL, - ColNames.ZLUMI, - ColNames.WIND_DIR, - ColNames.WIND_SPEED, - ColNames.TOT_SKY_COVER, - ColNames.OSKYCOVER, - ColNames.VIS, + Variables.NONE.col_name, + Variables.T_WB.col_name, + Variables.DPT.col_name, + Variables.DBT.col_name, + Variables.RH.col_name, + Variables.P_VAP.col_name, + Variables.HR.col_name, + Variables.EXTR_HOR_RAD.col_name, + Variables.HOR_IR_RAD.col_name, + Variables.GLOB_HOR_RAD.col_name, + Variables.DIR_NOR_RAD.col_name, + Variables.DIF_HOR_RAD.col_name, + Variables.GLOB_HOR_ILL.col_name, + Variables.DIR_NOR_ILL.col_name, + Variables.DIF_HOR_ILL.col_name, + Variables.ZLUMI.col_name, + Variables.WIND_DIR.col_name, + Variables.WIND_SPEED.col_name, + Variables.TOT_SKY_COVER.col_name, + Variables.OPAQUE_SKY_COVER.col_name, + Variables.VIS.col_name, ] variables_dropdown = [ - ColNames.T_WB, - ColNames.DPT, - ColNames.DBT, - ColNames.RH, - ColNames.P_VAP, - ColNames.HR, - ColNames.EXTR_HOR_RAD, - ColNames.HOR_IR_RAD, - ColNames.GLOB_HOR_RAD, - ColNames.DIR_NOR_RAD, - ColNames.DIF_HOR_RAD, - ColNames.GLOB_HOR_ILL, - ColNames.DIR_NOR_ILL, - ColNames.DIF_HOR_ILL, - ColNames.ZLUMI, - ColNames.WIND_DIR, - ColNames.WIND_SPEED, - ColNames.TOT_SKY_COVER, - ColNames.OSKYCOVER, - ColNames.VIS, + Variables.T_WB.col_name, + Variables.DPT.col_name, + Variables.DBT.col_name, + Variables.RH.col_name, + Variables.P_VAP.col_name, + Variables.HR.col_name, + Variables.EXTR_HOR_RAD.col_name, + Variables.HOR_IR_RAD.col_name, + Variables.GLOB_HOR_RAD.col_name, + Variables.DIR_NOR_RAD.col_name, + Variables.DIF_HOR_RAD.col_name, + Variables.GLOB_HOR_ILL.col_name, + Variables.DIR_NOR_ILL.col_name, + Variables.DIF_HOR_ILL.col_name, + Variables.ZLUMI.col_name, + Variables.WIND_DIR.col_name, + Variables.WIND_SPEED.col_name, + Variables.TOT_SKY_COVER.col_name, + Variables.OPAQUE_SKY_COVER.col_name, + Variables.VIS.col_name, ] variables_more_variables_dropdown = [ - ColNames.UTCI_SUN_WIND, - ColNames.UTCI_NO_SUN_WIND, - ColNames.UTCI_SUN_NO_WIND, - ColNames.UTCI_NO_SUN_NO_WIND, - ColNames.UTCI_SUN_WIND_CATEGORIES, - ColNames.UTCI_NOSUN_WIND_CATEGORIES, - ColNames.UTCI_SUN_NOWIND_CATEGORIES, - ColNames.UTCI_NOSUN_NOWIND_CATEGORIES, - ColNames.T_DP, - ColNames.ELEVATION, - ColNames.AZIMUTH, - ColNames.P_SAT, + Variables.UTCI_SUN_WIND.col_name, + Variables.UTCI_NO_SUN_WIND.col_name, + Variables.UTCI_SUN_NO_WIND.col_name, + Variables.UTCI_NO_SUN_NO_WIND.col_name, + Variables.UTCI_SUN_WIND_CATEGORIES.col_name, + Variables.UTCI_NOSUN_WIND_CATEGORIES.col_name, + Variables.UTCI_SUN_NOWIND_CATEGORIES.col_name, + Variables.UTCI_NOSUN_NOWIND_CATEGORIES.col_name, + Variables.T_DP.col_name, + Variables.ELEVATION.col_name, + Variables.AZIMUTH.col_name, + Variables.P_SAT.col_name, ] variables_sun_cloud_tab_explore_dropdown = [ - ColNames.EXTR_HOR_RAD, - ColNames.HOR_IR_RAD, - ColNames.GLOB_HOR_RAD, - ColNames.DIR_NOR_RAD, - ColNames.DIF_HOR_RAD, - ColNames.GLOB_HOR_ILL, - ColNames.DIR_NOR_ILL, - ColNames.DIF_HOR_ILL, - ColNames.ZLUMI, - ColNames.OSKYCOVER, + Variables.EXTR_HOR_RAD.col_name, + Variables.HOR_IR_RAD.col_name, + Variables.GLOB_HOR_RAD.col_name, + Variables.DIR_NOR_RAD.col_name, + Variables.DIF_HOR_RAD.col_name, + Variables.GLOB_HOR_ILL.col_name, + Variables.DIR_NOR_ILL.col_name, + Variables.DIF_HOR_ILL.col_name, + Variables.ZLUMI.col_name, + Variables.OPAQUE_SKY_COVER.col_name, ] variables_outdoor_dropdown = [ - ColNames.UTCI_SUN_WIND, - ColNames.UTCI_SUN_NO_WIND, - ColNames.UTCI_NO_SUN_WIND, - ColNames.UTCI_NO_SUN_NO_WIND, + Variables.UTCI_SUN_WIND.col_name, + Variables.UTCI_SUN_NO_WIND.col_name, + Variables.UTCI_NO_SUN_WIND.col_name, + Variables.UTCI_NO_SUN_NO_WIND.col_name, ] sun_cloud_tab_dropdown_names = { - mapping_dictionary[key][ColNames.NAME]: key + VariableInfo.from_col_name(key).get_name(): key for key in variables_sun_cloud_tab_dropdown } dropdown_names = { - mapping_dictionary[key][ColNames.NAME]: key for key in variables_dropdown + VariableInfo.from_col_name(key).get_name(): key for key in variables_dropdown } more_variables_dropdown = { - mapping_dictionary[key][ColNames.NAME]: key + VariableInfo.from_col_name(key).get_name(): key for key in variables_more_variables_dropdown } sun_cloud_tab_explore_dropdown_names = { - mapping_dictionary[key][ColNames.NAME]: key + VariableInfo.from_col_name(key).get_name(): key for key in variables_sun_cloud_tab_explore_dropdown } outdoor_dropdown_names = { - mapping_dictionary[key][ColNames.NAME]: key for key in variables_outdoor_dropdown + VariableInfo.from_col_name(key).get_name(): key + for key in variables_outdoor_dropdown } diff --git a/pages/lib/global_variables.py b/pages/lib/global_variables.py new file mode 100644 index 00000000..baf12b8d --- /dev/null +++ b/pages/lib/global_variables.py @@ -0,0 +1,663 @@ +from dataclasses import dataclass +from typing import Optional, List, Any +from config import UnitSystem + + +@dataclass +class IP: + """Metadata for the Imperial Units (IP)""" + + unit: str + range: List[float] + + +@dataclass +class VariableInfo: + """Column metadata: default unit/range/color represent the common display; + if SI or IP is provided, they will override the default values as needed.""" + + col_name: str + name: Optional[str] = None + unit: Optional[str] = None + range: Optional[List[float]] = None + color: Optional[List[Any]] = None + IP: Optional[IP] = None + + def get_name(self) -> Optional[str]: + """Returns the display name of the variable.""" + return self.name + + def get_unit(self, system: str) -> Optional[str]: + """Returns the unit of the variable based on the specified unit system.""" + if system == UnitSystem.IP and self.IP: + return self.IP.unit + return self.unit + + def get_range(self, system: str) -> Optional[List[float]]: + """Returns the valid value range of the variable based on the specified unit system.""" + if system == UnitSystem.IP and self.IP: + return self.IP.range + return self.range + + def get_color(self) -> Optional[List[Any]]: + """Returns the color settings of the variable, if available.""" + return self.color + + @classmethod + def from_col_name(cls, col_name: str) -> "VariableInfo": + """Returns the VariableInfo object by matching the column name.""" + for attr_name in dir(Variables): + variable = getattr(Variables, attr_name) + if isinstance(variable, cls) and variable.col_name == col_name: + return variable + raise KeyError(f"No VariableInfo found for col_name='{col_name}'") + + +class Variables: + # ==================== Basic Variables ==================== + NONE = VariableInfo(col_name="None", name="None", unit="", range=[]) + + # ==================== Time Related Variables ==================== + DOY = VariableInfo( + col_name="DOY", name="Day of the year", unit="days", range=[0, 365] + ) + DAY = VariableInfo(col_name="day", name="day", unit="", range=[1, 31]) + YEAR = VariableInfo(col_name="year") + PERIOD = VariableInfo(col_name="period") + MINUTE = VariableInfo(col_name="minute") + FAKE_YEAR = VariableInfo(col_name="fake_year") + MONTH_NAMES = VariableInfo(col_name="month_names") + UTC_TIME = VariableInfo(col_name="UTC_time") + MONTH = VariableInfo(col_name="month", name="months", unit="months", range=[1, 12]) + HOUR = VariableInfo( + col_name="hour", + name="Hour", + unit="h", + range=[1, 24], + color=[ + "#000000", + "#355e7e", + "#6b5c7b", + "#c06c84", + "#f8b195", + "#c92a42", + "#c92a42", + "#c92a42", + "#000000", + ], + ) + + # ==================== Location Related Variables ==================== + LAT = VariableInfo(col_name="lat") # Latitude + LON = VariableInfo(col_name="lon") # Longitude + CITY = VariableInfo(col_name="city") + COUNTRY = VariableInfo(col_name="country") + TIME_ZONE = VariableInfo(col_name="time_zone") + SITE_ELEVATION = VariableInfo(col_name="site_elevation") + + # ==================== Basic Meteorological Data ==================== + DBT = VariableInfo( + col_name="DBT", + name="Dry bulb temperature", + unit="°C", + range=[-40, 50], + color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + IP=IP(unit="°F", range=[-40, 122]), + ) + DPT = VariableInfo( + col_name="DPT", + name="Dew point temperature", + unit="°C", + range=[-50, 35], + color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + IP=IP(unit="°F", range=[-58, 95]), + ) + RH = VariableInfo( + col_name="RH", + name="Relative humidity", + unit="%", + range=[0, 100], + color=["#ffe600", "#00c8ff", "#0000ff"], + ) + + P_ATM = VariableInfo( + col_name="p_atm", + name="Atmospheric pressure", + unit="Pa", + range=[95000, 105000], + color=[ + "#ffffff", + "#b2f2ff", + "#33ddff", + "#00aaff", + "#0055ff", + "#0000ff", + "#aa00ff", + "#ff00ff", + "#cc0000", + "#ffaa00", + ], + IP=IP(unit="Psi", range=[95000 * 0.000145038, 105000 * 0.000145038]), + ) + + # ==================== Radiation Related Variables ==================== + EXTR_HOR_RAD = VariableInfo( + col_name="extr_hor_rad", + name="Extraterrestrial horizontal irradiation", + unit="Wh/m2", + range=[0, 1200], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + IP=IP(unit="Btu/ft2", range=[0, 1200 * 0.3169983306]), + ) + HOR_IR_RAD = VariableInfo( + col_name="hor_ir_rad", + name="Horizontal infrared radiation", + unit="Wh/m2", + range=[0, 500], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + IP=IP(unit="Btu/ft2", range=[0, 500 * 0.3169983306]), + ) + GLOB_HOR_RAD = VariableInfo( + col_name="glob_hor_rad", + name="Global horizontal radiation", + unit="Wh/m2", + range=[0, 1200], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + IP=IP(unit="Btu/ft2", range=[0, 1200 * 0.3169983306]), + ) + DIR_NOR_RAD = VariableInfo( + col_name="dir_nor_rad", + name="Direct normal radiation", + unit="Wh/m2", + range=[0, 1200], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + IP=IP(unit="Btu/ft2", range=[0, 1200 * 0.3169983306]), + ) + DIF_HOR_RAD = VariableInfo( + col_name="dif_hor_rad", + name="Diffuse horizontal radiation", + unit="Wh/m2", + range=[0, 1200], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + IP=IP(unit="Btu/ft2", range=[0, 1200 * 0.3169983306]), + ) + + # ==================== Lighting Related Variables ==================== + GLOB_HOR_ILL = VariableInfo( + col_name="glob_hor_ill", + name="Global horizontal illuminance", + unit="lux", + range=[0, 120000], + color=["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], + IP=IP(unit="fc", range=[0, 120000 * 0.0929]), + ) + DIR_NOR_ILL = VariableInfo( + col_name="dir_nor_ill", + name="Direct normal illuminance", + unit="lux", + range=[0, 120000], + color=["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], + IP=IP(unit="fc", range=[0, 120000 * 0.0929]), + ) + DIF_HOR_ILL = VariableInfo( + col_name="dif_hor_ill", + name="Diffuse horizontal illuminance", + unit="lux", + range=[0, 120000], + color=["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], + IP=IP(unit="fc", range=[0, 120000 * 0.0929]), + ) + + ZLUMI = VariableInfo( + col_name="Zlumi", + name="Zenith luminance", + unit="cd/m2", + range=[0, 60000], + color=["#730a8c", "#0d0db3", "#0f85be", "#0f85be", "#b11421", "#fdf130"], + IP=IP(unit="cd/ft2", range=[0, 60000 * 0.0929]), + ) + + # ==================== Wind Related Variables ==================== + WIND_DIR = VariableInfo( + col_name="wind_dir", + name="Wind direction", + unit="°deg", + range=[0, 360], + color=["#0072dd", "#00c420", "#eded00", "#be00d5", "#0072dd"], + ) + WIND_SPEED = VariableInfo( + col_name="wind_speed", + name="Wind speed", + unit="m/s", + range=[0, 20], + color=[ + "#D3D3D3", + "#b2f2ff", + "#33ddff", + "#00aaff", + "#0055ff", + "#0000ff", + "#aa00ff", + "#ff00ff", + "#cc0000", + "#ffaa00", + ], + IP=IP(unit="fpm", range=[0, 20 * 196.85039370078738]), + ) + + TOT_SKY_COVER = VariableInfo( + col_name="tot_sky_cover", + name="Total sky cover", + unit="tenths", + range=[0, 10], + color=[ + "#7ec9f3", + "#e6eae9", + "#c2c2c2", + ], + ) + OPAQUE_SKY_COVER = VariableInfo( + col_name="Oskycover", + name="Opaque sky cover", + unit="tenths", + range=[0, 10], + color=[ + "#7ec9f3", + "#e6eae9", + "#c2c2c2", + ], + ) + VIS = VariableInfo( + col_name="Vis", + name="Visibility", + unit="km", + range=[0, 100], + color=[ + "#7ec9f3", + "#e6eae9", + "#c2c2c2", + ], + IP=IP(unit="miles", range=[0, 100 * 0.6215]), + ) + + # ==================== Solar Position Related Variables ==================== + APPARENT_ZENITH = VariableInfo( + col_name="apparent_zenith", + name="Apparent zenith", + unit="°deg", + range=[0, 180], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + ) + ZENITH = VariableInfo( + col_name="zenith", + name="Zenith", + unit="°deg", + range=[0, 180], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + ) + APPARENT_ELEVATION = VariableInfo( + col_name="apparent_elevation", + name="Apparent elevation", + unit="°deg", + range=[-90, 90], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + ) + ELEVATION = VariableInfo( + col_name="elevation", + name="Elevation", + unit="°deg", + range=[-90, 90], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + ) + AZIMUTH = VariableInfo( + col_name="azimuth", + name="Azimuth", + unit="°deg", + range=[0, 360], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + ) + EQUATION_OF_TIME = VariableInfo( + col_name="equation_of_time", + name="Equation of time", + unit="°deg", + range=[-20, 20], + color=[ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ], + ) + + # ==================== UTCI Comfort Related Variables ==================== + UTCI_SUN_WIND = VariableInfo( + col_name="utci_Sun_Wind", + name="UTCI: Sun & Wind", + unit="°C", + range=[-70, 70], + color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + IP=IP(unit="°F", range=[-94, 158]), + ) + UTCI_NO_SUN_WIND = VariableInfo( + col_name="utci_noSun_Wind", + name="UTCI: no Sun & Wind", + unit="°C", + range=[-70, 70], + color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + IP=IP(unit="°F", range=[-94, 158]), + ) + UTCI_SUN_NO_WIND = VariableInfo( + col_name="utci_Sun_noWind", + name="UTCI: Sun & no Wind", + unit="°C", + range=[-70, 70], + color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + IP=IP(unit="°F", range=[-94, 158]), + ) + UTCI_NO_SUN_NO_WIND = VariableInfo( + col_name="utci_noSun_noWind", + name="UTCI: no Sun & no Wind", + unit="°C", + range=[-70, 70], + color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + IP=IP(unit="°F", range=[-94, 158]), + ) + + UTCI_SUN_WIND_CATEGORIES = VariableInfo( + col_name="utci_Sun_Wind_categories", + name="UTCI: Sun & Wind : categories", + unit="Thermal stress", + range=[-5, 4], + color=[ + [0, "#2B2977"], + [0.0555, "#2B2977"], + [0.0555, "#38429B"], + [0.1665, "#38429B"], + [0.1665, "#4253A4"], + [0.2775, "#4253A4"], + [0.2775, "#4B62AD"], + [0.3885, "#4B62AD"], + [0.3885, "#68B8E7"], + [0.4995, "#68B8E7"], + [0.4995, "#53B848"], + [0.6105, "#53B848"], + [0.6105, "#EE8522"], + [0.7215, "#EE8522"], + [0.7215, "#EA2C24"], + [0.8325, "#EA2C24"], + [0.8325, "#B12224"], + [0.9435, "#B12224"], + [0.9435, "#751613"], + [1.0, "#751613"], + ], + ) + UTCI_NOSUN_WIND_CATEGORIES = VariableInfo( + col_name="utci_noSun_Wind_categories", + name="UTCI: no Sun & Wind : categories", + unit="Thermal stress", + range=[-5, 4], + color=[ + [0, "#2B2977"], + [0.0555, "#2B2977"], + [0.0555, "#38429B"], + [0.1665, "#38429B"], + [0.1665, "#4253A4"], + [0.2775, "#4253A4"], + [0.2775, "#4B62AD"], + [0.3885, "#4B62AD"], + [0.3885, "#68B8E7"], + [0.4995, "#68B8E7"], + [0.4995, "#53B848"], + [0.6105, "#53B848"], + [0.6105, "#EE8522"], + [0.7215, "#EE8522"], + [0.7215, "#EA2C24"], + [0.8325, "#EA2C24"], + [0.8325, "#B12224"], + [0.9435, "#B12224"], + [0.9435, "#751613"], + [1.0, "#751613"], + ], + ) + UTCI_SUN_NOWIND_CATEGORIES = VariableInfo( + col_name="utci_Sun_noWind_categories", + name="UTCI: Sun & no Wind : categories", + unit="Thermal stress", + range=[-5, 4], + color=[ + [0, "#2B2977"], + [0.0555, "#2B2977"], + [0.0555, "#38429B"], + [0.1665, "#38429B"], + [0.1665, "#4253A4"], + [0.2775, "#4253A4"], + [0.2775, "#4B62AD"], + [0.3885, "#4B62AD"], + [0.3885, "#68B8E7"], + [0.4995, "#68B8E7"], + [0.4995, "#53B848"], + [0.6105, "#53B848"], + [0.6105, "#EE8522"], + [0.7215, "#EE8522"], + [0.7215, "#EA2C24"], + [0.8325, "#EA2C24"], + [0.8325, "#B12224"], + [0.9435, "#B12224"], + [0.9435, "#751613"], + [1.0, "#751613"], + ], + ) + UTCI_NOSUN_NOWIND_CATEGORIES = VariableInfo( + col_name="utci_noSun_noWind_categories", + name="UTCI: no Sun & no Wind : categories", + unit="Thermal stress", + range=[-5, 4], + color=[ + [0, "#2B2977"], + [0.0555, "#2B2977"], + [0.0555, "#38429B"], + [0.1665, "#38429B"], + [0.1665, "#4253A4"], + [0.2775, "#4253A4"], + [0.2775, "#4B62AD"], + [0.3885, "#4B62AD"], + [0.3885, "#68B8E7"], + [0.4995, "#68B8E7"], + [0.4995, "#53B848"], + [0.6105, "#53B848"], + [0.6105, "#EE8522"], + [0.7215, "#EE8522"], + [0.7215, "#EA2C24"], + [0.8325, "#EA2C24"], + [0.8325, "#B12224"], + [0.9435, "#B12224"], + [0.9435, "#751613"], + [1.0, "#751613"], + ], + ) + + # ==================== Additional Meteorological Data ==================== + P_VAP = VariableInfo( + col_name="p_vap", + name="Vapor partial pressure", + unit="Pa", + range=[0, 5000], + color=["#ffe600", "#00c8ff", "#0000ff"], + IP=IP(unit="Psi", range=[0, 5000 * 0.000145038]), + ) + P_SAT = VariableInfo( + col_name="p_sat", + name="Saturation pressure", + unit="Pa", + range=[0, 5000], + IP=IP(unit="Psi", range=[0, 5000 * 0.000145038]), + ) + HR = VariableInfo( + col_name="hr", + name="Absolute humidity", + unit="g water/kg dry air", + range=[0, 0.03 * 1000], + color=["#ffe600", "#00c8ff", "#0000ff"], + IP=IP(unit="lb water/klb dry air", range=[0, 0.03 * 1000]), + ) + T_WB = VariableInfo( + col_name="t_wb", + name="Wet bulb temperature", + unit="°C", + range=[-40, 50], + color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + IP=IP(unit="°F", range=[-40, 122]), + ) + T_DP = VariableInfo( + col_name="t_dp", + name="Dew point temperature", + unit="°C", + range=[-40, 50], + color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + IP=IP(unit="°F", range=[-40, 122]), + ) + EH = VariableInfo( + col_name="h", + name="Enthalpy", + unit="J/kg dry air", + range=[0, 110000], + color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + IP=IP(unit="Btu/lb dry air", range=[0, 110000 * 0.000429923]), + ) + + # ==================== Additional Humidity Data ==================== + HI_RH = VariableInfo(col_name="hiRH") + LO_RH = VariableInfo(col_name="loRH") + + # ==================== Additional Wind Data ==================== + WIND_SPEED_UTCI = VariableInfo(col_name="wind_speed_utci") + WIND_SPEED_UTCI_0 = VariableInfo(col_name="wind_speed_utci_0") + + # ==================== Additional Weather Data ==================== + CLOUD_HEIGHT = VariableInfo(col_name="Cheight") + PRECIPITATION_OBSERVATION = VariableInfo( + col_name="PWobs" + ) # Precipitation Observation + PRECIPITATION_CODES = VariableInfo(col_name="PWcodes") # Precipitation Codes + PRECIPITATION_WATER = VariableInfo(col_name="Pwater") # Precipitation Water + AEROSOL_OPTICAL_DEPTH = VariableInfo(col_name="AsolOptD") # Aerosol Optical Depth + SNOW_DEPTH = VariableInfo(col_name="SnowD") # Snow Depth + DAILY_SNOW = VariableInfo(col_name="DaySSnow") # Daily Snow + MRT = VariableInfo(col_name="MRT") + DELTA_MRT = VariableInfo(col_name="delta_mrt") + + # ==================== Adaptive Comfort Variables ==================== + ADAPTIVE_COMFORT = VariableInfo(col_name="adaptive_comfort") + ADAPTIVE_CMF_80_LOW = VariableInfo(col_name="adaptive_cmf_80_low") + ADAPTIVE_CMF_80_UP = VariableInfo(col_name="adaptive_cmf_80_up") + ADAPTIVE_CMF_90_LOW = VariableInfo(col_name="adaptive_cmf_90_low") + ADAPTIVE_CMF_90_UP = VariableInfo(col_name="adaptive_cmf_90_up") + ADAPTIVE_CMF_RMT = VariableInfo(col_name="adaptive_cmf_rmt") + NV_ALLOWED = VariableInfo(col_name="nv_allowed") + TMP_CMF = VariableInfo(col_name="tmp_cmf") + TMP_CMF_80_LOW = VariableInfo(col_name="tmp_cmf_80_low") + TMP_CMF_80_UP = VariableInfo(col_name="tmp_cmf_80_up") + TMP_CMF_90_LOW = VariableInfo(col_name="tmp_cmf_90_low") + TMP_CMF_90_UP = VariableInfo(col_name="tmp_cmf_90_up") + CONVERSION_FUNCTION = VariableInfo(col_name="conversion_function") + + # ==================== UI and Display Variables ==================== + COLOR = VariableInfo(col_name="color") + NAME = VariableInfo(col_name="name") + RANGE = VariableInfo(col_name="range") + UNIT = VariableInfo(col_name="unit") + TWENTY_FOUR_HOUR = VariableInfo(col_name="24h") + FIVE_MINUTE = VariableInfo(col_name="5min") + TIMES = VariableInfo(col_name="times") + PATH = VariableInfo(col_name="path") + FILE_NAME = VariableInfo(col_name="filename") + WIND_DIR_BINS = VariableInfo(col_name="WindDir_bins") + WIND_SPD_BINS = VariableInfo(col_name="WindSpd_bins") + TO_IMAGE_BUTTON_OPTIONS = VariableInfo(col_name="toImageButtonOptions") + INVERT = VariableInfo(col_name="invert") + FEATURES = VariableInfo(col_name="features") + GEOMETRY_COORDINATES = VariableInfo(col_name="geometry.coordinates") + PROP_ID = VariableInfo(col_name="prop_id") diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 71b11193..90c0f49e 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -2,7 +2,7 @@ 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 +from pages.lib.global_variables import Variables from config import DocLinks, UnitSystem from pages.lib.global_element_ids import ElementIds @@ -59,17 +59,17 @@ def create_navbar(): # Secondary Menu sub_links = [ dmc.NavLink( - label=page[ColNames.NAME], + label=page[Variables.NAME.col_name], leftSection=DashIconify( - icon=NavBarIcons.get_icon(page[ColNames.NAME]), width=20 + icon=NavBarIcons.get_icon(page[Variables.NAME.col_name]), width=20 ), - href=page[ColNames.PATH], - id=f"nav-{page[ColNames.PATH].replace('/', '')}", + href=page[Variables.PATH.col_name], + id=f"nav-{page[Variables.PATH.col_name].replace('/', '')}", active=False, styles=nav_link_styles, ) for page in dash.page_registry.values() - if page[ColNames.NAME] not in ["404"] + if page[Variables.NAME.col_name] not in ["404"] ] parent_group = dmc.NavLink( @@ -404,18 +404,18 @@ def toggle_navbar_and_width( @callback( [ - Output(f"nav-{page[ColNames.PATH].replace('/', '')}", "active") + Output(f"nav-{page[Variables.PATH.col_name].replace('/', '')}", "active") for page in dash.page_registry.values() - if page[ColNames.NAME] not in ["404"] + if page[Variables.NAME.col_name] not in ["404"] ], Input(ElementIds.MAIN_URL, "pathname"), prevent_initial_call=True, ) def update_nav_active_state(pathname): return [ - pathname == page[ColNames.PATH] + pathname == page[Variables.PATH.col_name] for page in dash.page_registry.values() - if page[ColNames.NAME] not in ["404"] + if page[Variables.NAME.col_name] not in ["404"] ] diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 4916381d..0d3bae38 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -5,20 +5,20 @@ from config import UnitSystem from pages.lib.utils import get_max_min_value -from pages.lib.global_scheme import mapping_dictionary import dash_bootstrap_components as dbc from .global_scheme import month_lst, template, tight_margins -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables, VariableInfo from .utils import code_timer, determine_month_and_hour_filter def violin(df, var, global_local, si_ip): """Return day night violin based on the 'var' col""" - mask_day = (df[ColNames.HOUR] >= 8) & (df[ColNames.HOUR] < 20) - mask_night = (df[ColNames.HOUR] < 8) | (df[ColNames.HOUR] >= 20) - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_name = mapping_dictionary[var][ColNames.NAME] + mask_day = (df[Variables.HOUR.col_name] >= 8) & (df[Variables.HOUR.col_name] < 20) + mask_night = (df[Variables.HOUR.col_name] < 8) | (df[Variables.HOUR.col_name] >= 20) + variable = VariableInfo.from_col_name(var) + var_unit = variable.get_unit(si_ip) + var_range = variable.get_range(si_ip) + var_name = variable.get_name() data_day = df.loc[mask_day, var] data_night = df.loc[mask_night, var] @@ -30,7 +30,7 @@ def violin(df, var, global_local, si_ip): fig = go.Figure() fig.add_trace( go.Violin( - x=df[ColNames.FAKE_YEAR], + x=df[Variables.FAKE_YEAR.col_name], y=data_day, line_color="#ffaa00", name="Day", @@ -42,7 +42,7 @@ def violin(df, var, global_local, si_ip): fig.add_trace( go.Violin( - x=df[ColNames.FAKE_YEAR], + x=df[Variables.FAKE_YEAR.col_name], y=data_night, line_color="#00264d", name="Night", @@ -84,10 +84,11 @@ def violin(df, var, global_local, si_ip): @code_timer def yearly_profile(df, var, global_local, si_ip): """Return yearly profile figure based on the 'var' col.""" - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_name = mapping_dictionary[var][ColNames.NAME] - var_color = mapping_dictionary[var][ColNames.COLOR] + variable = VariableInfo.from_col_name(var) + var_unit = variable.get_unit(si_ip) + var_range = variable.get_range(si_ip) + var_name = variable.get_name() + var_color = variable.get_color() if global_local == "global": # Set Global values for Max and minimum @@ -105,7 +106,7 @@ def yearly_profile(df, var, global_local, si_ip): ) trace1 = go.Bar( - x=df[ColNames.UTC_TIME].dt.date.unique(), + x=df[Variables.UTC_TIME.col_name].dt.date.unique(), y=dbt_day["max"] - dbt_day["min"], base=dbt_day["min"], marker_color=var_single_color, @@ -114,8 +115,8 @@ def yearly_profile(df, var, global_local, si_ip): customdata=np.stack( ( dbt_day["mean"], - df.iloc[::24, :][ColNames.MONTH_NAMES], - df.iloc[::24, :][ColNames.DAY], + df.iloc[::24, :][Variables.MONTH_NAMES.col_name], + df.iloc[::24, :][Variables.DAY.col_name], ), axis=-1, ), @@ -131,7 +132,7 @@ def yearly_profile(df, var, global_local, si_ip): ) trace2 = go.Scatter( - x=df[ColNames.UTC_TIME].dt.date.unique(), + x=df[Variables.UTC_TIME.col_name].dt.date.unique(), y=dbt_day["mean"], name="Average " + var_name, mode="lines", @@ -140,8 +141,8 @@ def yearly_profile(df, var, global_local, si_ip): customdata=np.stack( ( dbt_day["mean"], - df.iloc[::24, :][ColNames.MONTH_NAMES], - df.iloc[::24, :][ColNames.DAY], + df.iloc[::24, :][Variables.MONTH_NAMES.col_name], + df.iloc[::24, :][Variables.DAY.col_name], ), axis=-1, ), @@ -152,16 +153,28 @@ def yearly_profile(df, var, global_local, si_ip): ), ) - if var == ColNames.DBT: + if var == Variables.DBT.col_name: # plot ashrae adaptive comfort limits (80%) - lo80 = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_80_LOW].mean().values - hi80 = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_80_UP].mean().values - rmt = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_RMT].mean().values + lo80 = ( + df.groupby(Variables.DOY.col_name)[Variables.ADAPTIVE_CMF_80_LOW.col_name] + .mean() + .values + ) + hi80 = ( + df.groupby(Variables.DOY.col_name)[Variables.ADAPTIVE_CMF_80_UP.col_name] + .mean() + .values + ) + rmt = ( + df.groupby(Variables.DOY.col_name)[Variables.ADAPTIVE_CMF_RMT.col_name] + .mean() + .values + ) # set color https://github.com/CenterForTheBuiltEnvironment/clima/issues/113 implementation var_bar_colors = np.where((rmt > 40) | (rmt < 10), "lightgray", "darkgray") trace3 = go.Bar( - x=df[ColNames.UTC_TIME].dt.date.unique(), + x=df[Variables.UTC_TIME.col_name].dt.date.unique(), y=hi80 - lo80, base=lo80, name="ASHRAE adaptive comfort (80%)", @@ -173,11 +186,19 @@ def yearly_profile(df, var, global_local, si_ip): ) # plot ashrae adaptive comfort limits (90%) - lo90 = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_90_LOW].mean().values - hi90 = df.groupby(ColNames.DOY)[ColNames.ADAPTIVE_CMF_90_UP].mean().values + lo90 = ( + df.groupby(Variables.DOY.col_name)[Variables.ADAPTIVE_CMF_90_LOW.col_name] + .mean() + .values + ) + hi90 = ( + df.groupby(Variables.DOY.col_name)[Variables.ADAPTIVE_CMF_90_UP.col_name] + .mean() + .values + ) trace4 = go.Bar( - x=df[ColNames.UTC_TIME].dt.date.unique(), + x=df[Variables.UTC_TIME.col_name].dt.date.unique(), y=hi90 - lo90, base=lo90, name="ASHRAE adaptive comfort (90%)", @@ -189,17 +210,17 @@ def yearly_profile(df, var, global_local, si_ip): ) data = [trace3, trace4, trace1, trace2] - elif var == ColNames.RH: + elif var == Variables.RH.col_name: # plot relative Humidity limits (30-70%) lo_rh = [30] * 365 hi_rh = [70] * 365 - lo_rh_df = pd.DataFrame({ColNames.LO_RH: lo_rh}) - hi_rh_df = pd.DataFrame({ColNames.HI_RH: hi_rh}) + lo_rh_df = pd.DataFrame({Variables.LO_RH.col_name: lo_rh}) + hi_rh_df = pd.DataFrame({Variables.HI_RH.col_name: hi_rh}) trace3 = go.Bar( - x=df[ColNames.UTC_TIME].dt.date.unique(), - y=hi_rh_df[ColNames.HI_RH] - lo_rh_df[ColNames.LO_RH], - base=lo_rh_df[ColNames.LO_RH], + x=df[Variables.UTC_TIME.col_name].dt.date.unique(), + y=hi_rh_df[Variables.HI_RH.col_name] - lo_rh_df[Variables.LO_RH.col_name], + base=lo_rh_df[Variables.LO_RH.col_name], name="humidity comfort band", marker_opacity=0.3, marker_color="silver", @@ -244,10 +265,11 @@ def yearly_profile(df, var, global_local, si_ip): # @code_timer def daily_profile(df, var, global_local, si_ip): """Return the daily profile based on the 'var' col.""" - var_name = mapping_dictionary[var][ColNames.NAME] - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_color = mapping_dictionary[var][ColNames.COLOR] + variable = VariableInfo.from_col_name(var) + var_name = variable.get_name() + var_unit = variable.get_unit(si_ip) + var_range = variable.get_range(si_ip) + var_color = variable.get_color() if global_local == "global": # Set Global values for Max and minimum range_y = var_range @@ -258,7 +280,9 @@ def daily_profile(df, var, global_local, si_ip): var_single_color = var_color[len(var_color) // 2] var_month_ave = ( - df.groupby([ColNames.MONTH, ColNames.HOUR])[var].median().reset_index() + df.groupby([Variables.MONTH.col_name, Variables.HOUR.col_name])[var] + .median() + .reset_index() ) fig = make_subplots( rows=1, @@ -270,15 +294,20 @@ def daily_profile(df, var, global_local, si_ip): for i in range(12): fig.add_trace( go.Scatter( - x=df.loc[df[ColNames.MONTH] == i + 1, ColNames.HOUR], - y=df.loc[df[ColNames.MONTH] == i + 1, var], + x=df.loc[ + df[Variables.MONTH.col_name] == i + 1, Variables.HOUR.col_name + ], + y=df.loc[df[Variables.MONTH.col_name] == i + 1, var], mode="markers", marker_color=var_single_color, opacity=0.5, marker_size=3, name=month_lst[i], showlegend=False, - customdata=df.loc[df[ColNames.MONTH] == i + 1, ColNames.MONTH_NAMES], + customdata=df.loc[ + df[Variables.MONTH.col_name] == i + 1, + Variables.MONTH_NAMES.col_name, + ], hovertemplate=( "" + var @@ -294,9 +323,12 @@ def daily_profile(df, var, global_local, si_ip): fig.add_trace( go.Scatter( x=var_month_ave.loc[ - var_month_ave[ColNames.MONTH] == i + 1, ColNames.HOUR + var_month_ave[Variables.MONTH.col_name] == i + 1, + Variables.HOUR.col_name, + ], + y=var_month_ave.loc[ + var_month_ave[Variables.MONTH.col_name] == i + 1, var ], - y=var_month_ave.loc[var_month_ave[ColNames.MONTH] == i + 1, var], mode="lines", line_color=var_single_color, line_width=3, @@ -340,9 +372,10 @@ def heatmap_with_filter( title, ): """General function that returns a heatmap.""" - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_color = mapping_dictionary[var][ColNames.COLOR] + variable = VariableInfo.from_col_name(var) + var_unit = variable.get_unit(si_ip) + var_range = variable.get_range(si_ip) + var_color = variable.get_color() df = filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, var @@ -352,7 +385,7 @@ def heatmap_with_filter( month, hour, invert_month, invert_hour ) - if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: + if df.dropna(subset=[Variables.MONTH.col_name]).shape[0] == 0: return ( dbc.Alert( "No data is available in this location under these conditions. Please " @@ -372,13 +405,17 @@ def heatmap_with_filter( range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( - y=df[ColNames.HOUR] - 0.5, # Offset by 0.5 to center the hour labels - x=df[ColNames.UTC_TIME].dt.date, + y=df[Variables.HOUR.col_name] + - 0.5, # Offset by 0.5 to center the hour labels + x=df[Variables.UTC_TIME.col_name].dt.date, z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], - customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), + customdata=np.stack( + (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), hovertemplate=( "" + var @@ -417,9 +454,10 @@ def heatmap_with_filter( def heatmap(df, var, global_local, si_ip): """General function that returns a heatmap.""" - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] - var_color = mapping_dictionary[var][ColNames.COLOR] + variable = VariableInfo.from_col_name(var) + var_unit = variable.get_unit(si_ip) + var_range = variable.get_range(si_ip) + var_color = variable.get_color() if global_local == "global": # Set Global values for Max and minimum @@ -430,13 +468,16 @@ def heatmap(df, var, global_local, si_ip): range_z = [data_min, data_max] fig = go.Figure( data=go.Heatmap( - y=df[ColNames.HOUR], - x=df[ColNames.UTC_TIME].dt.date, + y=df[Variables.HOUR.col_name], + x=df[Variables.UTC_TIME.col_name].dt.date, z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], - customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), + customdata=np.stack( + (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), hovertemplate=( "" + var @@ -486,19 +527,29 @@ def wind_rose(df, title, month, hour, labels, si_ip): end_hour = hour[1] if start_month <= end_month: df = df.loc[ - (df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month) + (df[Variables.MONTH.col_name] >= start_month) + & (df[Variables.MONTH.col_name] <= end_month) ] else: df = df.loc[ - (df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month) + (df[Variables.MONTH.col_name] <= end_month) + | (df[Variables.MONTH.col_name] >= start_month) ] if start_hour <= end_hour: - df = df.loc[(df[ColNames.HOUR] > start_hour) & (df[ColNames.HOUR] <= end_hour)] + df = df.loc[ + (df[Variables.HOUR.col_name] > start_hour) + & (df[Variables.HOUR.col_name] <= end_hour) + ] else: - df = df.loc[(df[ColNames.HOUR] <= end_hour) | (df[ColNames.HOUR] >= start_hour)] + df = df.loc[ + (df[Variables.HOUR.col_name] <= end_hour) + | (df[Variables.HOUR.col_name] >= start_hour) + ] + + wind_speed_variable = VariableInfo.from_col_name(Variables.WIND_SPEED.col_name) - spd_colors = mapping_dictionary[ColNames.WIND_SPEED][ColNames.COLOR] - spd_unit = mapping_dictionary[ColNames.WIND_SPEED][si_ip][ColNames.UNIT] + spd_colors = wind_speed_variable.get_color() + spd_unit = wind_speed_variable.get_unit(si_ip) spd_bins = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] if si_ip == UnitSystem.IP: spd_bins = convert_bins(spd_bins) @@ -507,29 +558,36 @@ def wind_rose(df, title, month, hour, labels, si_ip): dir_bins = np.arange(-22.5 / 2, 360 + 22.5, 22.5) dir_labels = (dir_bins[:-1] + dir_bins[1:]) / 2 total_count = df.shape[0] - calm_count = df.query(f"{ColNames.WIND_SPEED} == 0").shape[0] + calm_count = df.query(f"{Variables.WIND_SPEED.col_name} == 0").shape[0] # Create a temporary DataFrame with binned data df_binned = df.assign( WindSpd_bins=lambda d: pd.cut( - d[ColNames.WIND_SPEED], bins=spd_bins, labels=spd_labels, right=True + d[Variables.WIND_SPEED.col_name], + bins=spd_bins, + labels=spd_labels, + right=True, ), WindDir_bins=lambda d: pd.cut( - d[ColNames.WIND_DIR], bins=dir_bins, labels=dir_labels, right=False + d[Variables.WIND_DIR.col_name], + bins=dir_bins, + labels=dir_labels, + right=False, ), ) # Rename the category in the 'WindDir_bins' column - df_binned[ColNames.WIND_DIR_BINS] = df_binned[ColNames.WIND_DIR_BINS].rename( - {360.0: 0.0} - ) + df_binned[Variables.WIND_DIR_BINS.col_name] = df_binned[ + Variables.WIND_DIR_BINS.col_name + ].rename({360.0: 0.0}) rose = ( df_binned.groupby( - by=[ColNames.WIND_SPD_BINS, ColNames.WIND_DIR_BINS], observed=False + by=[Variables.WIND_SPD_BINS.col_name, Variables.WIND_DIR_BINS.col_name], + observed=False, ) .size() - .unstack(level=ColNames.WIND_SPD_BINS) + .unstack(level=Variables.WIND_SPD_BINS.col_name) .fillna(0) .assign(calm=lambda d: calm_count / d.shape[0]) .sort_index(axis=1) @@ -633,7 +691,7 @@ def thermal_stress_stacked_barchart( month, hour, invert_month, invert_hour ) - if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: + if df.dropna(subset=[Variables.MONTH.col_name]).shape[0] == 0: return ( dbc.Alert( "No data is available in this location under these conditions. Please " @@ -646,7 +704,7 @@ def thermal_stress_stacked_barchart( isNormalized = True if normalize else False if isNormalized: new_df = ( - df.groupby(ColNames.MONTH)[var] + df.groupby(Variables.MONTH.col_name)[var] .value_counts(normalize=True) .unstack(var) .fillna(0) @@ -654,7 +712,12 @@ def thermal_stress_stacked_barchart( new_df = new_df.set_axis(categories, axis=1) new_df.reset_index(inplace=True) else: - new_df = df.groupby(ColNames.MONTH)[var].value_counts().unstack(var).fillna(0) + new_df = ( + df.groupby(Variables.MONTH.col_name)[var] + .value_counts() + .unstack(var) + .fillna(0) + ) new_df = new_df.set_axis(categories, axis=1) new_df.reset_index(inplace=True) @@ -731,13 +794,14 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): start_hour = time_filter_info[2][0] end_hour = time_filter_info[2][1] - filter_var = str(data_filter_info[1]) - filter_name = mapping_dictionary[filter_var][ColNames.NAME] - filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT] + filter_variable = VariableInfo.from_col_name(str(data_filter_info[1])) + filter_name = filter_variable.get_name() + filter_unit = filter_variable.get_unit(si_ip) - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - var_name = mapping_dictionary[var][ColNames.NAME] - var_color = mapping_dictionary[var][ColNames.COLOR] + var_variable = VariableInfo.from_col_name(var) + var_unit = var_variable.get_unit(si_ip) + var_name = var_variable.get_name() + var_color = var_variable.get_color() color_below = var_color[0] color_above = var_color[-1] @@ -758,13 +822,13 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): query = ( f"month=={str(i)} and ({filter_var}>={min_val} and {filter_var}<={max_val})" ) - a = new_df.query(query)[ColNames.DOY].count() + a = new_df.query(query)[Variables.DOY.col_name].count() month_in.append(a) query = f"month=={str(i)} and ({filter_var}<{min_val})" - b = new_df.query(query)[ColNames.DOY].count() + b = new_df.query(query)[Variables.DOY.col_name].count() month_below.append(b) query = f"month=={str(i)} and {filter_var}>{max_val}" - c = new_df.query(query)[ColNames.DOY].count() + c = new_df.query(query)[Variables.DOY.col_name].count() month_above.append(c) go.Figure() @@ -885,9 +949,9 @@ def filter_df_by_month_and_hour( if time_filter: # Month filter - time_filtering(df, start_month, end_month, ColNames.MONTH, var) + time_filtering(df, start_month, end_month, Variables.MONTH.col_name, var) # Hour filter - time_filtering(df, start_hour, end_hour, ColNames.HOUR, var) + time_filtering(df, start_hour, end_hour, Variables.HOUR.col_name, var) return df diff --git a/pages/lib/utils.py b/pages/lib/utils.py index c95c9084..ea553953 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -8,8 +8,8 @@ import dash_mantine_components as dmc from config import UnitSystem -from pages.lib.global_scheme import fig_config, mapping_dictionary, month_lst -from pages.lib.global_column_names import ColNames +from pages.lib.global_scheme import fig_config, month_lst +from pages.lib.global_variables import Variables, VariableInfo def code_timer(func): @@ -35,14 +35,14 @@ def generate_chart_name(tab_name, meta=None, custom_inputs=None, units=None): if units: custom_str += f"_{units}" if meta: - file_name = ( - f"{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_{tab_name}{custom_str}" - ) - figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = file_name + file_name = f"{meta[Variables.CITY.col_name]}_{meta[Variables.COUNTRY.col_name]}_{tab_name}{custom_str}" + figure_config[Variables.TO_IMAGE_BUTTON_OPTIONS.col_name][ + Variables.FILE_NAME.col_name + ] = file_name else: - figure_config[ColNames.TO_IMAGE_BUTTON_OPTIONS][ColNames.FILE_NAME] = ( - f"{tab_name}{custom_str}" - ) + figure_config[Variables.TO_IMAGE_BUTTON_OPTIONS.col_name][ + Variables.FILE_NAME.col_name + ] = f"{tab_name}{custom_str}" return figure_config @@ -59,12 +59,13 @@ def generate_units_degree(si_ip): def generate_custom_inputs(var): - if var in mapping_dictionary: - var_fullname = mapping_dictionary[var][ColNames.NAME] - custom_inputs = "".join(word.capitalize() for word in var_fullname.split(" ")) - return custom_inputs - else: - return None + try: + variable = VariableInfo.from_col_name(var) + if variable.name: + return "".join(word.capitalize() for word in variable.name.split(" ")) + except KeyError: + pass + return None def generate_custom_inputs_time(start_month, end_month, start_hour, end_hour): @@ -93,19 +94,24 @@ def generate_custom_inputs_explorer( month_names = [""] + month_lst start_month_abbr = month_names[int(start_month)] end_month_abbr = month_names[int(end_month)] - if var in mapping_dictionary: - var_fullname = "".join( - word.capitalize() - for word in mapping_dictionary[var][ColNames.NAME].split(" ") + try: + var_name = VariableInfo.from_col_name(var).get_name() + var_fullname = ( + "".join(word.capitalize() for word in var_name.split(" ")) + if var_name + else var ) - else: + except KeyError: var_fullname = var - if filter_var in mapping_dictionary: - filter_fullname = "".join( - word.capitalize() - for word in mapping_dictionary[filter_var][ColNames.NAME].split(" ") + + try: + filter_name = VariableInfo.from_col_name(filter_var).get_name() + filter_fullname = ( + "".join(word.capitalize() for word in filter_name.split(" ")) + if filter_name + else filter_var ) - else: + except KeyError: filter_fullname = filter_var custom_inputs = f"{var_fullname}_{start_month_abbr}-{end_month_abbr}_{start_hour:02d}-{end_hour:02d}_{filter_fullname}_{min_val}-{max_val}" return custom_inputs @@ -124,20 +130,19 @@ def generate_custom_inputs_psy( month_names = [""] + month_lst start_month_abbr = month_names[int(start_month)] end_month_abbr = month_names[int(end_month)] - if colorby_var in mapping_dictionary: - colorby_fullname = "".join( - word.capitalize() - for word in mapping_dictionary[colorby_var][ColNames.NAME].split(" ") - ) - else: - colorby_fullname = colorby_var - if data_filter_var in mapping_dictionary: - data_filter_fullname = "".join( - word.capitalize() - for word in mapping_dictionary[data_filter_var][ColNames.NAME].split(" ") - ) - else: - data_filter_fullname = data_filter_var + + def format_variable_name(var: str) -> str: + try: + variable = VariableInfo.from_col_name(var) + name = variable.get_name() + return ( + "".join(word.capitalize() for word in name.split(" ")) if name else var + ) + except KeyError: + return var + + colorby_fullname = format_variable_name(colorby_var) + data_filter_fullname = format_variable_name(data_filter_var) if colorby_var == "None": custom_inputs = f"{start_month_abbr}-{end_month_abbr}_{start_hour:02d}-{end_hour:02d}_{data_filter_fullname}_{min_val}-{max_val}" @@ -169,8 +174,6 @@ def title_with_tooltip(text, tooltip_text, id_button): ) else: return dmc.Group( - mt="md", - px="md", children=[ dmc.Title(text, order=3), ], @@ -210,13 +213,17 @@ def title_with_link( def summary_table_tmp_rh_tab(df, value, si_ip): df_summary = ( - df.groupby([ColNames.MONTH_NAMES, ColNames.MONTH])[value] + df.groupby([Variables.MONTH_NAMES.col_name, Variables.MONTH.col_name])[value] .describe(percentiles=[0.01, 0.25, 0.5, 0.75, 0.99]) .round(2) ) - df_summary = df_summary.reset_index(level=ColNames.MONTH_NAMES).sort_index() + df_summary = df_summary.reset_index( + level=Variables.MONTH_NAMES.col_name + ).sort_index() df_summary = df_summary.drop(["count"], axis=1) - df_summary = df_summary.rename(columns={ColNames.MONTH_NAMES: ColNames.MONTH}) + df_summary = df_summary.rename( + columns={Variables.MONTH_NAMES.col_name: Variables.MONTH.col_name} + ) df_sum = ( df[value] @@ -224,12 +231,15 @@ def summary_table_tmp_rh_tab(df, value, si_ip): .round(2) .to_frame() ) - df_sum = df_sum.T.assign(count="Year").rename(columns={"count": ColNames.MONTH}) + df_sum = df_sum.T.assign(count="Year").rename( + columns={"count": Variables.MONTH.col_name} + ) df_summary = pd.concat([df_summary, df_sum]) unit = ( - mapping_dictionary[value][si_ip][ColNames.UNIT] + VariableInfo.from_col_name(value) + .get_unit(si_ip) .replace("", "") .replace("", "") ) @@ -237,7 +247,7 @@ def summary_table_tmp_rh_tab(df, value, si_ip): columns=[ ( {"name": i, "id": i} - if i == ColNames.MONTH + if i == Variables.MONTH.col_name else {"name": f"{i} ({unit})", "id": i} ) for i in df_summary.columns @@ -258,10 +268,14 @@ def summary_table_tmp_rh_tab(df, value, si_ip): def determine_month_and_hour_filter(month, hour, invert_month, invert_hour): start_month, end_month = month - if invert_month == [ColNames.INVERT] and (start_month != 1 or end_month != 12): + if invert_month == [Variables.INVERT.col_name] and ( + start_month != 1 or end_month != 12 + ): end_month, start_month = month start_hour, end_hour = hour - if invert_hour == [ColNames.INVERT] and (start_hour != 0 or end_hour != 24): + if invert_hour == [Variables.INVERT.col_name] and ( + start_hour != 0 or end_hour != 24 + ): end_hour, start_hour = hour return start_month, end_month, start_hour, end_hour diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 4cd2cc94..9f185202 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -9,13 +9,12 @@ from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.global_scheme import ( template, - mapping_dictionary, tight_margins, month_lst, ) from pages.lib.utils import get_max_min_value from pages.lib.template_graphs import filter_df_by_month_and_hour -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables, VariableInfo from pages.lib.global_element_ids import ElementIds from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames @@ -286,8 +285,8 @@ def nv_heatmap( month, hour, invert_month, invert_hour ) - var = ColNames.DBT - filter_var = ColNames.DPT + var = Variables.DBT.col_name + filter_var = Variables.DPT.col_name if dbt_data_filter and (min_dbt_val <= max_dbt_val): df.loc[(df[var] < min_dbt_val) | (df[var] > max_dbt_val), var] = None @@ -295,7 +294,7 @@ def nv_heatmap( if dpt_data_filter: df.loc[(df[filter_var] < -200) | (df[filter_var] > max_dpt_val), var] = None - if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: + if df.dropna(subset=[Variables.MONTH.col_name]).shape[0] == 0: return ( dmc.Alert( title="Notice", @@ -314,17 +313,20 @@ def nv_heatmap( df, time_filter, month, hour, invert_month, invert_hour, var ) - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + variable = VariableInfo.from_col_name(var) + filter = VariableInfo.from_col_name(filter_var) - filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT] + var_unit = variable.get_unit(si_ip) - var_range = mapping_dictionary[var][si_ip][ColNames.RANGE] + filter_unit = filter.get_unit(si_ip) - var_name = mapping_dictionary[var][ColNames.NAME] + var_range = variable.get_range(si_ip) - filter_name = mapping_dictionary[filter_var][ColNames.NAME] + var_name = variable.get_name() - var_color = mapping_dictionary[var][ColNames.COLOR] + filter_name = filter.get_name() + + var_color = variable.get_color() if global_local == "global": range_z = var_range @@ -348,15 +350,19 @@ def nv_heatmap( fig = go.Figure( data=go.Heatmap( - y=df[ColNames.HOUR] - 0.5, # Offset by 0.5 to center the hour labels - x=df[ColNames.UTC_TIME].dt.date, + y=df[Variables.HOUR.col_name] + - 0.5, # Offset by 0.5 to center the hour labels + x=df[Variables.UTC_TIME.col_name].dt.date, z=df[var], colorscale=var_color, zmin=range_z[0], zmax=range_z[1], connectgaps=False, hoverongaps=False, - customdata=np.stack((df[ColNames.MONTH_NAMES], df[ColNames.DAY]), axis=-1), + customdata=np.stack( + (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), hovertemplate=( "" + var @@ -455,19 +461,22 @@ def nv_bar_chart( month, hour, invert_month, invert_hour ) - var = ColNames.DBT - filter_var = ColNames.DPT + var = Variables.DBT.col_name + filter_var = Variables.DPT.col_name + + variable = VariableInfo.from_col_name(var) + filter = VariableInfo.from_col_name(filter_var) - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] - filter_unit = mapping_dictionary[filter_var][si_ip][ColNames.UNIT] + var_unit = variable.get_unit(si_ip) + filter_unit = filter.get_unit(si_ip) - var_name = mapping_dictionary[var][ColNames.NAME] + var_name = variable.get_name() - filter_name = mapping_dictionary[filter_var][ColNames.NAME] + filter_name = filter.get_name() color_in = "dodgerblue" - df[ColNames.NV_ALLOWED] = 1 + df[Variables.NV_ALLOWED.col_name] = 1 df = filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, "nv_allowed" @@ -475,20 +484,27 @@ def nv_bar_chart( # this should be the total after filtering by time tot_month_hours = ( - df.groupby(df[ColNames.UTC_TIME].dt.month)[ColNames.NV_ALLOWED].sum().values + df.groupby(df[Variables.UTC_TIME.col_name].dt.month)[ + Variables.NV_ALLOWED.col_name + ] + .sum() + .values ) if dbt_data_filter and (min_dbt_val <= max_dbt_val): df.loc[ - (df[var] < min_dbt_val) | (df[var] > max_dbt_val), ColNames.NV_ALLOWED + (df[var] < min_dbt_val) | (df[var] > max_dbt_val), + Variables.NV_ALLOWED.col_name, ] = 0 if dpt_data_filter: - df.loc[(df[filter_var] > max_dpt_val), ColNames.NV_ALLOWED] = 0 + df.loc[(df[filter_var] > max_dpt_val), Variables.NV_ALLOWED.col_name] = 0 n_hours_nv_allowed = ( - df.dropna(subset=ColNames.NV_ALLOWED) - .groupby(df[ColNames.UTC_TIME].dt.month)[ColNames.NV_ALLOWED] + df.dropna(subset=Variables.NV_ALLOWED.col_name) + .groupby(df[Variables.UTC_TIME.col_name].dt.month)[ + Variables.NV_ALLOWED.col_name + ] .sum() .values ) @@ -498,7 +514,7 @@ def nv_bar_chart( if not normalize: fig = go.Figure( go.Bar( - x=df[ColNames.MONTH_NAMES].unique(), + x=df[Variables.MONTH_NAMES.col_name].unique(), y=n_hours_nv_allowed, name="", marker_color=color_in, @@ -520,7 +536,7 @@ def nv_bar_chart( else: trace1 = go.Bar( - x=df[ColNames.MONTH_NAMES].unique(), + x=df[Variables.MONTH_NAMES.col_name].unique(), y=per_time_nv_allowed, name="", marker_color=color_in, diff --git a/pages/outdoor.py b/pages/outdoor.py index a7d9ba4d..d0483c47 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -7,7 +7,7 @@ from config import PageUrls, DocLinks, PageInfo from pages.lib.global_element_ids import ElementIds -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames from pages.lib.global_scheme import ( @@ -211,10 +211,10 @@ def update_outdoor_comfort_output(_, df): Description of the best weather condition(s). """ cols = [ - ColNames.UTCI_NOSUN_WIND_CATEGORIES, - ColNames.UTCI_NOSUN_NOWIND_CATEGORIES, - ColNames.UTCI_SUN_WIND_CATEGORIES, - ColNames.UTCI_SUN_NOWIND_CATEGORIES, + Variables.UTCI_NOSUN_WIND_CATEGORIES.col_name, + Variables.UTCI_NOSUN_NOWIND_CATEGORIES.col_name, + Variables.UTCI_SUN_WIND_CATEGORIES.col_name, + Variables.UTCI_SUN_NOWIND_CATEGORIES.col_name, ] cols_with_the_highest_number_of_zero = [] highest_count = 0 diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 4aca01e1..b17969a9 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -12,7 +12,7 @@ from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.utils import get_max_min_value from pages.lib.global_element_ids import ElementIds -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables, VariableInfo from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames from pages.lib.global_scheme import ( @@ -21,7 +21,6 @@ more_variables_dropdown, sun_cloud_tab_explore_dropdown_names, template, - mapping_dictionary, tight_margins, ) from pages.lib.template_graphs import filter_df_by_month_and_hour @@ -143,7 +142,7 @@ def inputs(): dropdown( id=ElementIds.PSY_VAR_DROPDOWN, options=dropdown_names, - value=ColNames.RH, + value=Variables.RH.col_name, ), flex=1, ), @@ -262,7 +261,7 @@ def update_psych_chart( mask = (df[data_filter_var] >= max_val) & (df[data_filter_var] <= min_val) df[mask] = None - if df.dropna(subset=[ColNames.MONTH]).shape[0] == 0: + if df.dropna(subset=[Variables.MONTH.col_name]).shape[0] == 0: return ( dmc.Alert( "No data is available in this location under these conditions. Please " @@ -279,24 +278,27 @@ def update_psych_chart( elif var == "Frequency": var_color = ["rgba(255,255,255,0)", "rgb(0,150,255)", "rgb(0,0,150)"] else: - var_unit = mapping_dictionary[var][si_ip][ColNames.UNIT] + var_unit = VariableInfo.from_col_name(var).get_unit(si_ip) - var_name = mapping_dictionary[var][ColNames.NAME] + var_name = VariableInfo.from_col_name(var).get_name() - var_color = mapping_dictionary[var][ColNames.COLOR] + var_color = VariableInfo.from_col_name(var).get_color() if global_local == "global": # Set Global values for Max and minimum - var_range_x = mapping_dictionary[ColNames.DBT][si_ip][ColNames.RANGE] - var_range_y = mapping_dictionary[ColNames.HR][si_ip][ColNames.RANGE] + variable_x = VariableInfo.from_col_name(Variables.DBT.col_name) + variable_y = VariableInfo.from_col_name(Variables.HR.col_name) + + var_range_x = variable_x.get_range(si_ip) + var_range_y = variable_y.get_range(si_ip) else: # Set maximum and minimum according to data - data_max, data_min = get_max_min_value(df[ColNames.DBT]) + data_max, data_min = get_max_min_value(df[Variables.DBT.col_name]) var_range_x = [data_min, data_max] - data_max = round(df[ColNames.HR].max(), 4) - data_min = round(df[ColNames.HR].min(), 4) + data_max = round(df[Variables.HR.col_name].max(), 4) + data_min = round(df[Variables.HR.col_name].min(), 4) var_range_y = [data_min * 1000, data_max * 1000] title = "Psychrometric Chart" @@ -312,7 +314,7 @@ def update_psych_chart( hr_list = np.vectorize(psy.psy_ta_rh)(dbt_list, rh) hr_df = pd.DataFrame.from_records(hr_list) name = "rh" + str(rh) - rh_df[name] = hr_df[ColNames.HR] + rh_df[name] = hr_df[Variables.HR.col_name] fig = go.Figure() @@ -342,13 +344,13 @@ def update_psych_chart( ) ) - df_hr_multiply = list(df[ColNames.HR]) + df_hr_multiply = list(df[Variables.HR.col_name]) for k in range(len(df_hr_multiply)): df_hr_multiply[k] = df_hr_multiply[k] * 1000 if var == "None": fig.add_trace( go.Scatter( - x=df[ColNames.DBT], + x=df[Variables.DBT.col_name], y=df_hr_multiply, showlegend=False, mode="markers", @@ -358,16 +360,18 @@ def update_psych_chart( showscale=False, opacity=0.2, ), - hovertemplate=mapping_dictionary[ColNames.DBT][ColNames.NAME] + hovertemplate=VariableInfo.from_col_name( + Variables.DBT.col_name + ).get_name() + ": %{x:.2f}" - + mapping_dictionary[ColNames.DBT][ColNames.NAME], + + VariableInfo.from_col_name(Variables.DBT.col_name).get_name(), name="", ) ) elif var == "Frequency": fig.add_trace( go.Histogram2d( - x=df[ColNames.DBT], + x=df[Variables.DBT.col_name], y=df_hr_multiply, name="", colorscale=var_color, @@ -411,7 +415,7 @@ def update_psych_chart( fig.add_trace( go.Scatter( - x=df[ColNames.DBT], + x=df[Variables.DBT.col_name], y=df_hr_multiply, showlegend=False, mode="markers", @@ -424,23 +428,25 @@ def update_psych_chart( colorbar=var_colorbar, ), customdata=np.stack( - (df[ColNames.RH], df["h"], df[var], df["t_dp"]), axis=-1 + (df[Variables.RH.col_name], df["h"], df[var], df["t_dp"]), axis=-1 ), - hovertemplate=mapping_dictionary[ColNames.DBT][ColNames.NAME] + hovertemplate=VariableInfo.from_col_name( + Variables.DBT.col_name + ).get_name() + ": %{x:.2f}" - + mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT] + + VariableInfo.from_col_name(Variables.DBT.col_name).get_unit(si_ip) + "
" - + mapping_dictionary[ColNames.RH][ColNames.NAME] + + VariableInfo.from_col_name(Variables.RH.col_name).get_name() + ": %{customdata[0]:.2f}" - + mapping_dictionary[ColNames.RH][si_ip][ColNames.UNIT] + + VariableInfo.from_col_name(Variables.RH.col_name).get_unit(si_ip) + "
" - + mapping_dictionary["h"][ColNames.NAME] + + VariableInfo.from_col_name("h").get_name() + ": %{customdata[1]:.2f}" - + mapping_dictionary["h"][si_ip][ColNames.UNIT] + + VariableInfo.from_col_name("h").get_unit(si_ip) + "
" - + mapping_dictionary["t_dp"][ColNames.NAME] + + VariableInfo.from_col_name("t_dp").get_name() + ": %{customdata[3]:.2f}" - + mapping_dictionary["t_dp"][si_ip][ColNames.UNIT] + + VariableInfo.from_col_name("t_dp").get_unit(si_ip) + "
" + "
" + var_name @@ -451,10 +457,14 @@ def update_psych_chart( ) xtitle_name = ( - "Temperature" + " " + mapping_dictionary[ColNames.DBT][si_ip][ColNames.UNIT] + "Temperature" + + " " + + VariableInfo.from_col_name(Variables.DBT.col_name).get_unit(si_ip) ) ytitle_name = ( - "Humidity Ratio" + " " + mapping_dictionary[ColNames.HR][si_ip][ColNames.UNIT] + "Humidity Ratio" + + " " + + VariableInfo.from_col_name(Variables.HR.col_name).get_unit(si_ip) ) fig.update_layout(template=template, margin=tight_margins) fig.update_xaxes( diff --git a/pages/select.py b/pages/select.py index 63db2152..498cc67b 100644 --- a/pages/select.py +++ b/pages/select.py @@ -10,13 +10,12 @@ from dash_extensions.enrich import Serverside, Output, Input, State, html, dcc, callback from pandas import json_normalize -from pages.lib.extract_df import convert_data +from pages.lib.extract_df import convert_df_units from pages.lib.extract_df import create_df, get_data, get_location_info -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables from pages.lib.global_element_ids import ElementIds from pages.lib.global_tab_names import TabNames -from pages.lib.global_scheme import mapping_dictionary -from config import PageUrls, PageInfo, UnitSystem +from config import PageUrls, PageInfo from pages.lib.utils import generate_chart_name dash.register_page( @@ -149,7 +148,7 @@ def submitted_data( """Process the uploaded file or download the EPW from the URL""" ctx = dash.callback_context - if ctx.triggered[0][ColNames.PROP_ID] == "modal-yes-button.n_clicks": + if ctx.triggered[0][Variables.PROP_ID.col_name] == "modal-yes-button.n_clicks": lines = get_data(url_store) if lines is None: return ( @@ -171,7 +170,7 @@ def submitted_data( ) elif ( - ctx.triggered[0][ColNames.PROP_ID] == "upload-data.contents" + ctx.triggered[0][Variables.PROP_ID.col_name] == "upload-data.contents" and list_of_contents is not None ): content_type, content_string = list_of_contents[0].split(",") @@ -231,9 +230,9 @@ def submitted_data( def switch_si_ip(_, si_ip_input, url_store, lines): if lines is not None: df, _ = create_df(lines, url_store) - map_json = json.dumps(mapping_dictionary) - if si_ip_input == UnitSystem.IP: - map_json = convert_data(df, map_json) + + df = convert_df_units(df, si_ip_input) + return Serverside(df), si_ip_input else: return ( @@ -290,7 +289,10 @@ def enable_tabs_when_data_is_loaded(meta, data): False, False, True, # changelog always disabled - "Current Location: " + meta[ColNames.CITY] + ", " + meta[ColNames.COUNTRY], + "Current Location: " + + meta[Variables.CITY.col_name] + + ", " + + meta[Variables.COUNTRY.col_name], ) @@ -340,11 +342,11 @@ def plot_location_epw_files(pathname): with open("./assets/data/epw_location.json", encoding="utf8") as data_file: data = json.load(data_file) - df = json_normalize(data[ColNames.FEATURES]) - df[[ColNames.LON, ColNames.LAT]] = pd.DataFrame( - df[ColNames.GEOMETRY_COORDINATES].tolist() + df = json_normalize(data[Variables.FEATURES.col_name]) + df[[Variables.LON.col_name, Variables.LAT.col_name]] = pd.DataFrame( + df[Variables.GEOMETRY_COORDINATES.col_name].tolist() ) - df[ColNames.LAT] += 0.010 + df[Variables.LAT.col_name] += 0.010 df = df.rename(columns={"properties.epw": "Source"}) fig = px.scatter_mapbox( @@ -363,7 +365,7 @@ def plot_location_epw_files(pathname): df_one_building, lat="lat", lon="lon", - hover_name=df_one_building[ColNames.NAME], + hover_name=df_one_building[Variables.NAME.col_name], color_discrete_sequence=["#4895ef"], hover_data=[ "period", diff --git a/pages/summary.py b/pages/summary.py index 7f11a30e..e6512738 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -9,9 +9,9 @@ from config import PageUrls, DocLinks, PageInfo, UnitSystem from pages.lib.charts_summary import world_map from pages.lib.extract_df import get_data -from pages.lib.global_scheme import template, tight_margins, mapping_dictionary +from pages.lib.global_scheme import template, tight_margins from pages.lib.template_graphs import violin -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables, VariableInfo from pages.lib.global_element_ids import ElementIds from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames @@ -196,11 +196,13 @@ 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]}" - lat = f"Latitude: {meta[ColNames.LAT]}" + location = ( + f"Location: {meta[Variables.CITY.col_name]}, {meta[Variables.COUNTRY.col_name]}" + ) + lon = f" Longitude: {meta[Variables.LON.col_name]}" + lat = f"Latitude: {meta[Variables.LAT.col_name]}" - site_elevation = round(float(meta[ColNames.SITE_ELEVATION]), 2) + site_elevation = round(float(meta[Variables.SITE_ELEVATION.col_name]), 2) if si_ip != UnitSystem.SI: site_elevation = round(site_elevation * 3.281, 2) elevation = f"Elevation above sea level: {site_elevation} ft" @@ -209,14 +211,14 @@ def update_location_info(ts, df, meta, si_ip): elevation = f"Elevation above sea level: {site_elevation} m" period = "" - if meta[ColNames.PERIOD]: - start, stop = meta[ColNames.PERIOD].split("-") + if meta[Variables.PERIOD.col_name]: + start, stop = meta[Variables.PERIOD.col_name].split("-") period = f"This file is based on data collected between {start} and {stop}" climate_text = "" try: r = requests.get( - f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[ColNames.LAT]}/{meta[ColNames.LON]}" + f"http://climateapi.scottpinkelman.com/api/v1/location/{meta[Variables.LAT.col_name]}/{meta[Variables.LON.col_name]}" ) if r.status_code == 200: j = r.json()["return_values"][0] @@ -226,27 +228,27 @@ def update_location_info(ts, df, meta, si_ip): # 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 - ].replace("", "").replace("", "") + total_solar_rad_value = round(df[Variables.GLOB_HOR_RAD.col_name].sum() / 1000, 2) + total_solar_rad_unit = "k" + VariableInfo.from_col_name( + Variables.GLOB_HOR_RAD.col_name + ).get_unit(si_ip).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() + glob_sum = df[Variables.GLOB_HOR_RAD.col_name].sum() diffuse_percentage = ( - round(df[ColNames.DIF_HOR_RAD].sum() / glob_sum * 100, 1) if glob_sum > 0 else 0 + round(df[Variables.DIF_HOR_RAD.col_name].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] + tmp_unit = VariableInfo.from_col_name(Variables.DBT.col_name).get_unit(si_ip) - 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}" + average_yearly_tmp = f"Average yearly temperature: {df[Variables.DBT.col_name].mean().round(1)} {tmp_unit}" + hottest_yearly_tmp = f"Hottest yearly temperature (99%): {df[Variables.DBT.col_name].quantile(0.99).round(1)} {tmp_unit}" + coldest_yearly_tmp = f"Coldest yearly temperature (1%): {df[Variables.DBT.col_name].quantile(0.01).round(1)} {tmp_unit}" return [ dmc.Text(location, fw=700), @@ -298,18 +300,18 @@ def degree_day_chart(ts, n_clicks, df, meta, hdd_value, cdd_value, si_ip): color_cdd = "dodgerblue" hdd_array, cdd_array = [], [] - months = df[ColNames.MONTH_NAMES].unique() + months = df[Variables.MONTH_NAMES.col_name].unique() for i in range(1, 13): query_month = "month==" a = df.query(query_month + str(i) + " and DBT<=" + str(hdd_setpoint))[ - ColNames.DBT + Variables.DBT.col_name ].sub(hdd_setpoint) hdd_array.append(int(a.sum(skipna=True) / 24)) a = df.query(query_month + str(i) + " and DBT>=" + str(cdd_setpoint))[ - ColNames.DBT + Variables.DBT.col_name ].sub(cdd_setpoint) cdd_array.append(int(a.sum(skipna=True) / 24)) @@ -383,7 +385,7 @@ def update_violin_tdb(ts, global_local, df, meta, si_ip): return dcc.Graph( id=ElementIds.TDB_PROFILE_GRAPH, config=generate_chart_name(TabNames.DRY_BULB_TEMPERATURE, meta, units), - figure=violin(df, ColNames.DBT, global_local, si_ip), + figure=violin(df, Variables.DBT.col_name, global_local, si_ip), ) @@ -405,7 +407,7 @@ def update_tab_wind(ts, global_local, df, meta, si_ip): return dcc.Graph( id=ElementIds.WIND_PROFILE_GRAPH, config=generate_chart_name(TabNames.WIND_SPEED, meta, units), - figure=violin(df, ColNames.WIND_SPEED, global_local, si_ip), + figure=violin(df, Variables.WIND_SPEED.col_name, global_local, si_ip), ) @@ -427,7 +429,7 @@ def update_tab_rh(ts, global_local, df, meta, si_ip): return dcc.Graph( id=ElementIds.RH_PROFILE_GRAPH, config=generate_chart_name(TabNames.RELATIVE_HUMIDITY, meta, units), - figure=violin(df, ColNames.RH, global_local, si_ip), + figure=violin(df, Variables.RH.col_name, global_local, si_ip), ) @@ -449,7 +451,7 @@ def update_tab_gh_rad(ts, global_local, df, meta, si_ip): return dcc.Graph( id=ElementIds.GH_RAD_PROFILE_GRAPH, config=generate_chart_name(TabNames.GLOBAL_HORIZONTAL_RADIATION, meta, units), - figure=violin(df, ColNames.GLOB_HOR_RAD, global_local, si_ip), + figure=violin(df, Variables.GLOB_HOR_RAD.col_name, global_local, si_ip), ) @@ -470,12 +472,12 @@ def download_clima_dataframe(n_clicks, df, meta, si_ip): if si_ip == UnitSystem.SI: return dcc.send_data_frame( df.to_csv, - f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_SIunit.csv", + f"df_{meta[Variables.CITY.col_name]}_{meta[Variables.COUNTRY.col_name]}_Clima_SIunit.csv", ) else: return dcc.send_data_frame( df.to_csv, - f"df_{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}_Clima_IPunit.csv", + f"df_{meta[Variables.CITY.col_name]}_{meta[Variables.COUNTRY.col_name]}_Clima_IPunit.csv", ) else: print("df not loaded yet") @@ -496,7 +498,7 @@ def download_epw(n_clicks, meta): lines[0] = lines[0].replace("b'", "") return dict( content="\n".join(lines), - filename=f"{meta[ColNames.CITY]}_{meta[ColNames.COUNTRY]}.epw", + filename=f"{meta[Variables.CITY.col_name]}_{meta[Variables.COUNTRY.col_name]}.epw", ) else: raise PreventUpdate diff --git a/pages/sun.py b/pages/sun.py index 5307acc2..3999dac3 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -8,7 +8,7 @@ from dash import dcc from dash_extensions.enrich import Output, Input, State, callback -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames from config import PageUrls, DocLinks, PageInfo, UnitSystem @@ -203,7 +203,7 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): # Cloud Cover cover = barchart( - df, ColNames.TOT_SKY_COVER, [False], [False, "", 3, 7], True, si_ip + df, Variables.TOT_SKY_COVER.col_name, [False], [False, "", 3, 7], True, si_ip ) cover = cover.update_layout( margin=tight_margins, diff --git a/pages/t_rh.py b/pages/t_rh.py index a9c01b63..932a9df1 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -5,7 +5,7 @@ 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 -from pages.lib.global_column_names import ColNames +from pages.lib.global_variables import Variables from pages.lib.global_element_ids import ElementIds from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames @@ -101,7 +101,7 @@ def layout(): ) def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): if dd_value == dropdown_names[var_to_plot[0]]: - dbt_yearly = yearly_profile(df, ColNames.DBT, global_local, si_ip) + dbt_yearly = yearly_profile(df, Variables.DBT.col_name, global_local, si_ip) dbt_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) units = generate_units_degree(si_ip) return dcc.Graph( @@ -111,7 +111,7 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): figure=dbt_yearly, ) else: - rh_yearly = yearly_profile(df, ColNames.RH, global_local, si_ip) + rh_yearly = yearly_profile(df, Variables.RH.col_name, global_local, si_ip) rh_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) units = generate_units(si_ip) return dcc.Graph( @@ -143,15 +143,15 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): figure=daily_profile( df[ [ - ColNames.DBT, - ColNames.HOUR, - ColNames.UTC_TIME, - ColNames.MONTH_NAMES, - ColNames.DAY, - ColNames.MONTH, + Variables.DBT.col_name, + Variables.HOUR.col_name, + Variables.UTC_TIME.col_name, + Variables.MONTH_NAMES.col_name, + Variables.DAY.col_name, + Variables.MONTH.col_name, ] ], - ColNames.DBT, + Variables.DBT.col_name, global_local, si_ip, ), @@ -163,15 +163,15 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): figure=daily_profile( df[ [ - ColNames.RH, - ColNames.HOUR, - ColNames.UTC_TIME, - ColNames.MONTH_NAMES, - ColNames.DAY, - ColNames.MONTH, + Variables.RH.col_name, + Variables.HOUR.col_name, + Variables.UTC_TIME.col_name, + Variables.MONTH_NAMES.col_name, + Variables.DAY.col_name, + Variables.MONTH.col_name, ] ], - ColNames.RH, + Variables.RH.col_name, global_local, si_ip, ), @@ -202,14 +202,14 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): figure=heatmap( df[ [ - ColNames.DBT, - ColNames.HOUR, - ColNames.UTC_TIME, - ColNames.MONTH_NAMES, - ColNames.DAY, + Variables.DBT.col_name, + Variables.HOUR.col_name, + Variables.UTC_TIME.col_name, + Variables.MONTH_NAMES.col_name, + Variables.DAY.col_name, ] ], - ColNames.DBT, + Variables.DBT.col_name, global_local, si_ip, ), @@ -221,14 +221,14 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): figure=heatmap( df[ [ - ColNames.RH, - ColNames.HOUR, - ColNames.UTC_TIME, - ColNames.MONTH_NAMES, - ColNames.DAY, + Variables.RH.col_name, + Variables.HOUR.col_name, + Variables.UTC_TIME.col_name, + Variables.MONTH_NAMES.col_name, + Variables.DAY.col_name, ] ], - ColNames.RH, + Variables.RH.col_name, global_local, si_ip, ), @@ -249,7 +249,14 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): def update_table(_, dd_value, df, si_ip): """Update the contents of descriptive statistics table.""" return summary_table_tmp_rh_tab( - df[[ColNames.MONTH, ColNames.HOUR, dd_value, ColNames.MONTH_NAMES]], + df[ + [ + Variables.MONTH.col_name, + Variables.HOUR.col_name, + dd_value, + Variables.MONTH_NAMES.col_name, + ] + ], dd_value, si_ip, ) diff --git a/pages/wind.py b/pages/wind.py index 195f4b6f..e9d0d9f6 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -7,7 +7,7 @@ from config import PageUrls, DocLinks, PageInfo 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_variables import Variables from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames from pages.lib.utils import ( @@ -222,7 +222,7 @@ def custom_wind_rose(): children=[ dmc.Title( "Start Month:", - order=6, + order=5, w="8rem", ta="right", ), @@ -359,7 +359,7 @@ def update_annual_wind_rose(_, df, meta, si_ip): ], ) def update_tab_wind_speed(_, global_local, df, meta, si_ip): - speed = heatmap(df, ColNames.WIND_SPEED, global_local, si_ip) + speed = heatmap(df, Variables.WIND_SPEED.col_name, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( config=generate_chart_name(TabNames.WIND_SPEED, meta, units), @@ -377,7 +377,7 @@ def update_tab_wind_speed(_, global_local, df, meta, si_ip): ], ) def update_tab_wind_direction(global_local, df, meta, si_ip): - direction = heatmap(df, ColNames.WIND_DIR, global_local, si_ip) + direction = heatmap(df, Variables.WIND_DIR.col_name, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( config=generate_chart_name(TabNames.WIND_DIRECTION, meta, units), @@ -410,16 +410,24 @@ def update_custom_wind_rose( if start_month <= end_month: df = df.loc[ - (df[ColNames.MONTH] >= start_month) & (df[ColNames.MONTH] <= end_month) + (df[Variables.MONTH.col_name] >= start_month) + & (df[Variables.MONTH.col_name] <= end_month) ] else: df = df.loc[ - (df[ColNames.MONTH] <= end_month) | (df[ColNames.MONTH] >= start_month) + (df[Variables.MONTH.col_name] <= end_month) + | (df[Variables.MONTH.col_name] >= start_month) ] if start_hour <= end_hour: - df = df.loc[(df[ColNames.HOUR] >= start_hour) & (df[ColNames.HOUR] <= end_hour)] + df = df.loc[ + (df[Variables.HOUR.col_name] >= start_hour) + & (df[Variables.HOUR.col_name] <= end_hour) + ] else: - df = df.loc[(df[ColNames.HOUR] <= end_hour) | (df[ColNames.HOUR] >= start_hour)] + df = df.loc[ + (df[Variables.HOUR.col_name] <= end_hour) + | (df[Variables.HOUR.col_name] >= start_hour) + ] custom = wind_rose( df, "", [start_month, end_month], [start_hour, end_hour], True, si_ip @@ -466,31 +474,32 @@ def update_seasonal_graphs(_, df, meta, si_ip): summer = wind_rose(df, "", summer_months, hours, False, si_ip) fall = wind_rose(df, "", fall_months, hours, False, si_ip) - query_calm_wind = f"{ColNames.WIND_SPEED} == 0" + query_calm_wind = f"{Variables.WIND_SPEED.col_name} == 0" winter_df = df.loc[ - (df[ColNames.MONTH] <= winter_months[1]) - | (df[ColNames.MONTH] >= winter_months[0]) + (df[Variables.MONTH.col_name] <= winter_months[1]) + | (df[Variables.MONTH.col_name] >= winter_months[0]) ] winter_total_count = winter_df.shape[0] winter_calm_count = winter_df.query(query_calm_wind).shape[0] spring_df = df.loc[ - (df[ColNames.MONTH] >= spring_months[0]) - & (df[ColNames.MONTH] <= spring_months[1]) + (df[Variables.MONTH.col_name] >= spring_months[0]) + & (df[Variables.MONTH.col_name] <= spring_months[1]) ] spring_total_count = spring_df.shape[0] spring_calm_count = spring_df.query(query_calm_wind).shape[0] summer_df = df.loc[ - (df[ColNames.MONTH] >= summer_months[0]) - & (df[ColNames.MONTH] <= summer_months[1]) + (df[Variables.MONTH.col_name] >= summer_months[0]) + & (df[Variables.MONTH.col_name] <= summer_months[1]) ] summer_total_count = summer_df.shape[0] summer_calm_count = summer_df.query(query_calm_wind).shape[0] fall_df = df.loc[ - (df[ColNames.MONTH] >= fall_months[0]) & (df[ColNames.MONTH] <= fall_months[1]) + (df[Variables.MONTH.col_name] >= fall_months[0]) + & (df[Variables.MONTH.col_name] <= fall_months[1]) ] fall_total_count = fall_df.shape[0] fall_calm_count = fall_df.query(query_calm_wind).shape[0] @@ -579,24 +588,25 @@ def update_daily_graphs(_, df, meta, si_ip): noon = wind_rose(df, "", months, noon_times, False, si_ip) night = wind_rose(df, "", months, night_times, True, si_ip) - query_calm_wind = f"{ColNames.WIND_SPEED} == 0" + query_calm_wind = f"{Variables.WIND_SPEED.col_name} == 0" morning_df = df.loc[ - (df[ColNames.HOUR] >= morning_times[0]) - & (df[ColNames.HOUR] <= morning_times[1]) + (df[Variables.HOUR.col_name] >= morning_times[0]) + & (df[Variables.HOUR.col_name] <= morning_times[1]) ] morning_total_count = morning_df.shape[0] morning_calm_count = morning_df.query(query_calm_wind).shape[0] noon_df = df.loc[ - (df[ColNames.HOUR] >= morning_times[0]) - & (df[ColNames.HOUR] <= morning_times[1]) + (df[Variables.HOUR.col_name] >= morning_times[0]) + & (df[Variables.HOUR.col_name] <= morning_times[1]) ] noon_total_count = noon_df.shape[0] noon_calm_count = noon_df.query(query_calm_wind).shape[0] night_df = df.loc[ - (df[ColNames.HOUR] <= night_times[1]) | (df[ColNames.HOUR] >= night_times[0]) + (df[Variables.HOUR.col_name] <= night_times[1]) + | (df[Variables.HOUR.col_name] >= night_times[0]) ] night_total_count = night_df.shape[0] night_calm_count = night_df.query(query_calm_wind).shape[0] From 7f1b3805d741d93f412e93b0392931b463f985fa Mon Sep 17 00:00:00 2001 From: Kira-Liu00 Date: Wed, 8 Oct 2025 15:42:02 +1100 Subject: [PATCH 104/163] fix: implement fixed heatmap legend range for issue #224 --- pages/lib/extract_df.py | 12 +++++++++++- pages/lib/template_graphs.py | 1 + pages/natural_ventilation.py | 13 ++++++++----- tests/node/cypress/e2e/spec.cy.js | 2 +- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index ded1c783..37ebde3a 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -5,6 +5,7 @@ import zipfile from datetime import timedelta from urllib.request import Request, urlopen +from functools import lru_cache import logging import numpy as np @@ -49,6 +50,13 @@ def get_data(source_url): return None +@lru_cache(maxsize=64) +def get_global_temp_range(location: str, col_name: str) -> tuple[float, float]: + df_all = get_data(location) # Take the full-year data + series = df_all[col_name].replace([np.inf, -np.inf], np.nan).dropna() + return float(series.min()), float(series.max()) + + @code_timer def get_location_info(lst, file_name): """Extract and clean the data. Return a pandas data from a url.""" @@ -254,7 +262,9 @@ def create_df(lst, file_name): Variables.MONTH.col_name, Variables.HOUR.col_name, ] - ].astype(int) + ].astype( + int + ) # Add in DOY df_doy = ( diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 0d3bae38..4dcc499b 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -370,6 +370,7 @@ def heatmap_with_filter( invert_month, invert_hour, title, + z_range=None, ): """General function that returns a heatmap.""" variable = VariableInfo.from_col_name(var) diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 9f185202..fee08e70 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -1,5 +1,6 @@ import dash from dash import dcc +from dash import no_update import dash_mantine_components as dmc from dash_extensions.enrich import Output, Input, State, callback @@ -12,7 +13,6 @@ tight_margins, month_lst, ) -from pages.lib.utils import get_max_min_value from pages.lib.template_graphs import filter_df_by_month_and_hour from pages.lib.global_variables import Variables, VariableInfo from pages.lib.global_element_ids import ElementIds @@ -278,6 +278,8 @@ def nv_heatmap( invert_hour, si_ip, ): + if df is None: + return no_update # enable or disable button apply filter DPT dpt_data_filter = enable_dew_point_data_filter(condensation_enabled) @@ -328,11 +330,10 @@ def nv_heatmap( var_color = variable.get_color() - if global_local == "global": - range_z = var_range + if si_ip == UnitSystem.IP: + range_z = [32.0, 86.0] else: - data_max, data_min = get_max_min_value(df[var]) - range_z = [data_min, data_max] + range_z = [0.0, 30.0] title = ( f"Hours when the {var_name} is in the range {min_dbt_val} to" @@ -420,6 +421,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.ID_NATURAL_VENTILATION_GLOBAL_LOCAL_RADIO_INPUT, "value"), Input(ElementIds.SWITCHES_INPUT, "checked"), Input(ElementIds.ENABLE_CONDENSATION, "value"), ], @@ -441,6 +443,7 @@ def nv_bar_chart( time_filter, dbt_data_filter, click_dpt_filter, + global_local, normalize, condensation_enabled, df, diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index 38dea4fe..3de4a4aa 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -22,7 +22,7 @@ function click_tab(name) { } function load_epw() { - cy.get('input[type=file]').selectFile('test.epw', {force: true}); + cy.get('input[type=file]').selectFile('test.epw', { force: true }); } describe('Clima', () => { From c562e2f9523debd894dbdc82b9a3ca27529a354a Mon Sep 17 00:00:00 2001 From: Kira-Liu00 Date: Thu, 9 Oct 2025 15:20:21 +1100 Subject: [PATCH 105/163] fix: remove the unused function --- pages/lib/extract_df.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 37ebde3a..3bf724b7 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -5,7 +5,6 @@ import zipfile from datetime import timedelta from urllib.request import Request, urlopen -from functools import lru_cache import logging import numpy as np @@ -50,13 +49,6 @@ def get_data(source_url): return None -@lru_cache(maxsize=64) -def get_global_temp_range(location: str, col_name: str) -> tuple[float, float]: - df_all = get_data(location) # Take the full-year data - series = df_all[col_name].replace([np.inf, -np.inf], np.nan).dropna() - return float(series.min()), float(series.max()) - - @code_timer def get_location_info(lst, file_name): """Extract and clean the data. Return a pandas data from a url.""" From e46d76211e6fead54bb55703e17b7ac7ec1d748d Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Tue, 14 Oct 2025 18:30:11 +1100 Subject: [PATCH 106/163] fix: format code to pass ruff test. --- pages/lib/extract_df.py | 4 +--- pages/natural_ventilation.py | 2 -- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index 3bf724b7..ded1c783 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -254,9 +254,7 @@ def create_df(lst, file_name): Variables.MONTH.col_name, Variables.HOUR.col_name, ] - ].astype( - int - ) + ].astype(int) # Add in DOY df_doy = ( diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index fee08e70..a2485bbf 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -322,8 +322,6 @@ def nv_heatmap( filter_unit = filter.get_unit(si_ip) - var_range = variable.get_range(si_ip) - var_name = variable.get_name() filter_name = filter.get_name() From 176ca08271aef63d063addf91d4f4e08698e0009 Mon Sep 17 00:00:00 2001 From: FengW01 Date: Wed, 15 Oct 2025 14:09:03 +1100 Subject: [PATCH 107/163] chore(a11y): update color schemes for color-blind assessment #110 --- pages/lib/global_scheme.py | 20 +++++--------------- pages/lib/global_variables.py | 24 +++++++----------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index 36fd2d00..606b871d 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,5 +1,6 @@ import plotly.io as pio - +import colorcet as cc +from plotly.colors import sequential as pseq from pages.lib.global_variables import Variables, VariableInfo # Colors Dictionary @@ -16,23 +17,12 @@ ] light_colors = ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"] bright_colors = ["#730a8c", "#0d0db3", "#0f85be", "#0f85be", "#b11421", "#fdf130"] -wind_speed_color = [ - "#ffffff", - "#b2f2ff", - "#33ddff", - "#00aaff", - "#0055ff", - "#0000ff", - "#aa00ff", - "#ff00ff", - "#cc0000", - "#ffaa00", -] -wind_dir_color = ["#0072dd", "#00c420", "#eded00", "#be00d5", "#0072dd"] +wind_speed_color = list(cc.CET_L19) +wind_dir_color = list(pseq.Viridis) cloud_colors = [ + "#08306b", "#7ec9f3", "#e6eae9", - "#c2c2c2", ] utci_categories_color = [ # Let first 10% (0.1) of the values have color rgb(0, 0, 0) diff --git a/pages/lib/global_variables.py b/pages/lib/global_variables.py index baf12b8d..b8e467be 100644 --- a/pages/lib/global_variables.py +++ b/pages/lib/global_variables.py @@ -1,7 +1,8 @@ from dataclasses import dataclass from typing import Optional, List, Any from config import UnitSystem - +import colorcet as cc +from plotly.colors import sequential as pseq @dataclass class IP: @@ -263,25 +264,14 @@ class Variables: name="Wind direction", unit="°deg", range=[0, 360], - color=["#0072dd", "#00c420", "#eded00", "#be00d5", "#0072dd"], + color=list(pseq.Viridis), ) WIND_SPEED = VariableInfo( col_name="wind_speed", name="Wind speed", unit="m/s", range=[0, 20], - color=[ - "#D3D3D3", - "#b2f2ff", - "#33ddff", - "#00aaff", - "#0055ff", - "#0000ff", - "#aa00ff", - "#ff00ff", - "#cc0000", - "#ffaa00", - ], + color=list(cc.CET_L19), IP=IP(unit="fpm", range=[0, 20 * 196.85039370078738]), ) @@ -291,9 +281,9 @@ class Variables: unit="tenths", range=[0, 10], color=[ + "#08306b", "#7ec9f3", "#e6eae9", - "#c2c2c2", ], ) OPAQUE_SKY_COVER = VariableInfo( @@ -302,9 +292,9 @@ class Variables: unit="tenths", range=[0, 10], color=[ + "#08306b", "#7ec9f3", "#e6eae9", - "#c2c2c2", ], ) VIS = VariableInfo( @@ -313,9 +303,9 @@ class Variables: unit="km", range=[0, 100], color=[ + "#08306b", "#7ec9f3", "#e6eae9", - "#c2c2c2", ], IP=IP(unit="miles", range=[0, 100 * 0.6215]), ) From cb79f39b6dc67e59f7254305dfc2be20e7d05802 Mon Sep 17 00:00:00 2001 From: FengW01 Date: Wed, 15 Oct 2025 15:23:34 +1100 Subject: [PATCH 108/163] chore(a11y): update color schemes for color-blind assessment #110 --- pages/lib/global_scheme.py | 2 +- pages/lib/global_variables.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index 606b871d..f301ddf4 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -17,7 +17,7 @@ ] light_colors = ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"] bright_colors = ["#730a8c", "#0d0db3", "#0f85be", "#0f85be", "#b11421", "#fdf130"] -wind_speed_color = list(cc.CET_L19) +wind_speed_color = [cc.CET_L19[int(round(i*(len(cc.CET_L19)-1)/(9)))] for i in range(10)] wind_dir_color = list(pseq.Viridis) cloud_colors = [ "#08306b", diff --git a/pages/lib/global_variables.py b/pages/lib/global_variables.py index b8e467be..e29344cf 100644 --- a/pages/lib/global_variables.py +++ b/pages/lib/global_variables.py @@ -271,7 +271,7 @@ class Variables: name="Wind speed", unit="m/s", range=[0, 20], - color=list(cc.CET_L19), + color=[cc.CET_L19[int(round(i*(len(cc.CET_L19)-1)/(9)))] for i in range(10)], IP=IP(unit="fpm", range=[0, 20 * 196.85039370078738]), ) From 1e502cd1fdd43be8c0f16766fa8f670b25c243e7 Mon Sep 17 00:00:00 2001 From: FengW01 Date: Mon, 20 Oct 2025 17:20:09 +1100 Subject: [PATCH 109/163] fix(color): unify wind speed color scale across all charts --- pages/lib/global_scheme.py | 24 +++++++++++++++++++++++- pages/lib/template_graphs.py | 12 ++++++++++++ requirements.txt | 1 + 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index f301ddf4..775c8f8b 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -17,7 +17,29 @@ ] light_colors = ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"] bright_colors = ["#730a8c", "#0d0db3", "#0f85be", "#0f85be", "#b11421", "#fdf130"] -wind_speed_color = [cc.CET_L19[int(round(i*(len(cc.CET_L19)-1)/(9)))] for i in range(10)] +wind_speed_color = [ + cc.CET_L19[int(round(i * (len(cc.CET_L19) - 1) / (9)))] for i in range(10) +] + +WIND_ROSE_BINS = [0.0, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7] + + +def _stepped_colorscale_from_bins(bins, colors): + vmin, vmax = bins[0], bins[-1] + span = (vmax - vmin) or 1.0 + cs = [] + for i, c in enumerate(colors): + left = (bins[i] - vmin) / span + right = ((bins[i + 1] if i + 1 < len(bins) else vmax) - vmin) / span + cs.append((left, c)) + cs.append((right, c)) + return cs + + +wind_speed_colorscale_rose = _stepped_colorscale_from_bins( + WIND_ROSE_BINS, wind_speed_color +) + wind_dir_color = list(pseq.Viridis) cloud_colors = [ "#08306b", diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 4dcc499b..8477d5f3 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -430,6 +430,12 @@ def heatmap_with_filter( ) ) + if var == Variables.WIND_SPEED.col_name: + spd_bins = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] + if si_ip == UnitSystem.IP: + spd_bins = convert_bins(spd_bins) + fig.update_traces(zmin=0, zmax=spd_bins[-2]) + fig.update_xaxes(dtick="M1", tickformat="%b", ticklabelmode="period") fig.update_yaxes(title_text="Hour") @@ -492,6 +498,12 @@ def heatmap(df, var, global_local, si_ip): ) ) + if var == Variables.WIND_SPEED.col_name: + spd_bins = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] + if si_ip == UnitSystem.IP: + spd_bins = convert_bins(spd_bins) + fig.update_traces(zmin=0, zmax=spd_bins[-2]) + fig.update_xaxes(dtick="M1", tickformat="%b", ticklabelmode="period") fig.update_yaxes(title_text="Hour") diff --git a/requirements.txt b/requirements.txt index 14cdb68c..aca379ed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ certifi==2025.7.14 charset-normalizer==3.4.2 cleanpy==0.5.1 click==8.2.1 +colorcet>=3.0.1,<4 dash==3.2.0 dash-bootstrap-components==1.2.0 dash-core-components==2.0.0 From b4a51cb0b1cf72edc538bc00e53daf6446d43ff9 Mon Sep 17 00:00:00 2001 From: FengW01 Date: Wed, 22 Oct 2025 12:53:13 +1100 Subject: [PATCH 110/163] chore(deps): add colorcet to Pipfile; refactor(global_variables): remove duplicate code --- Pipfile | 1 + Pipfile.lock | 11 +- pages/lib/global_scheme.py | 4 +- pages/lib/global_variables.py | 320 ++++++++++------------------------ pages/lib/template_graphs.py | 6 +- 5 files changed, 110 insertions(+), 232 deletions(-) diff --git a/Pipfile b/Pipfile index 0c01f3de..210142c0 100644 --- a/Pipfile +++ b/Pipfile @@ -16,6 +16,7 @@ pandas = "==2.2.0" numpy = "==1.26.3" dash-iconify = "*" scipy = "==1.12.0" +colorcet = "<4,>=3.0.1" [dev-packages] cleanpy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 8e475b6d..ce60fcad 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4f09fe1d5fd82b15503fd3b0c1606e5e6139df983488fdc78c72555daf01e167" + "sha256": "79b1a6efc301bcc68eb75b61790df1ac8051751236f6b505086c26572d4d3458" }, "pipfile-spec": 6, "requires": { @@ -133,6 +133,15 @@ "markers": "python_version >= '3.10'", "version": "==8.2.1" }, + "colorcet": { + "hashes": [ + "sha256:2921b3cd81a2288aaf2d63dbc0ce3c26dcd882e8c389cc505d6886bf7aa9a4eb", + "sha256:2a7d59cc8d0f7938eeedd08aad3152b5319b4ba3bcb7a612398cc17a384cb296" + ], + "index": "pypi", + "markers": "python_version >= '3.7'", + "version": "==3.1.0" + }, "dash": { "hashes": [ "sha256:4c1819588d83bed2cbcf5807daa5c2380c8c85789a6935a733f018f04ad8a6a2", diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index 775c8f8b..ab2ade5c 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,5 +1,6 @@ import plotly.io as pio import colorcet as cc +import numpy as np from plotly.colors import sequential as pseq from pages.lib.global_variables import Variables, VariableInfo @@ -17,11 +18,12 @@ ] light_colors = ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"] bright_colors = ["#730a8c", "#0d0db3", "#0f85be", "#0f85be", "#b11421", "#fdf130"] +# Take 10 colors at equal intervals (including both ends) wind_speed_color = [ cc.CET_L19[int(round(i * (len(cc.CET_L19) - 1) / (9)))] for i in range(10) ] -WIND_ROSE_BINS = [0.0, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7] +WIND_ROSE_BINS = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] def _stepped_colorscale_from_bins(bins, colors): diff --git a/pages/lib/global_variables.py b/pages/lib/global_variables.py index e29344cf..5a101cf0 100644 --- a/pages/lib/global_variables.py +++ b/pages/lib/global_variables.py @@ -4,6 +4,56 @@ import colorcet as cc from plotly.colors import sequential as pseq +# ---------- Shared palettes & conversion constants ---------- +BLUE_RED_YELLOW = ["#00b3ff", "#000082", "#ff0000", "#ffff00"] +DRY_HUMID = ["#ffe600", "#00c8ff", "#0000ff"] +SUN_COLORS = [ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", +] +LIGHT_COLORS = ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"] +SKY_COVER_TRI = ["#08306b", "#7ec9f3", "#e6eae9"] + +# Evenly sample 10 colors (including both endpoints) from CET_L19 +CET_L19_10 = [ + cc.CET_L19[int(round(i * (len(cc.CET_L19) - 1) / (9)))] for i in range(10) +] + +# Unit conversions +PA_TO_PSI = 0.000145038 +WHM2_TO_BTUFT2 = 0.3169983306 +LUX_TO_FC = 0.0929 + +# UTCI category colorscale +UTCI_CATEGORIES_SCALE = [ + [0, "#2B2977"], + [0.0555, "#2B2977"], + [0.0555, "#38429B"], + [0.1665, "#38429B"], + [0.1665, "#4253A4"], + [0.2775, "#4253A4"], + [0.2775, "#4B62AD"], + [0.3885, "#4B62AD"], + [0.3885, "#68B8E7"], + [0.4995, "#68B8E7"], + [0.4995, "#53B848"], + [0.6105, "#53B848"], + [0.6105, "#EE8522"], + [0.7215, "#EE8522"], + [0.7215, "#EA2C24"], + [0.8325, "#EA2C24"], + [0.8325, "#B12224"], + [0.9435, "#B12224"], + [0.9435, "#751613"], + [1.0, "#751613"], +] + + @dataclass class IP: """Metadata for the Imperial Units (IP)""" @@ -102,7 +152,7 @@ class Variables: name="Dry bulb temperature", unit="°C", range=[-40, 50], - color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + color=BLUE_RED_YELLOW, IP=IP(unit="°F", range=[-40, 122]), ) DPT = VariableInfo( @@ -110,7 +160,7 @@ class Variables: name="Dew point temperature", unit="°C", range=[-50, 35], - color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + color=BLUE_RED_YELLOW, IP=IP(unit="°F", range=[-58, 95]), ) RH = VariableInfo( @@ -118,7 +168,7 @@ class Variables: name="Relative humidity", unit="%", range=[0, 100], - color=["#ffe600", "#00c8ff", "#0000ff"], + color=DRY_HUMID, ) P_ATM = VariableInfo( @@ -138,7 +188,7 @@ class Variables: "#cc0000", "#ffaa00", ], - IP=IP(unit="Psi", range=[95000 * 0.000145038, 105000 * 0.000145038]), + IP=IP(unit="Psi", range=[95000 * PA_TO_PSI, 105000 * PA_TO_PSI]), ) # ==================== Radiation Related Variables ==================== @@ -147,80 +197,40 @@ class Variables: name="Extraterrestrial horizontal irradiation", unit="Wh/m2", range=[0, 1200], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - IP=IP(unit="Btu/ft2", range=[0, 1200 * 0.3169983306]), + color=SUN_COLORS, + IP=IP(unit="Btu/ft2", range=[0, 1200 * WHM2_TO_BTUFT2]), ) HOR_IR_RAD = VariableInfo( col_name="hor_ir_rad", name="Horizontal infrared radiation", unit="Wh/m2", range=[0, 500], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - IP=IP(unit="Btu/ft2", range=[0, 500 * 0.3169983306]), + color=SUN_COLORS, + IP=IP(unit="Btu/ft2", range=[0, 500 * WHM2_TO_BTUFT2]), ) GLOB_HOR_RAD = VariableInfo( col_name="glob_hor_rad", name="Global horizontal radiation", unit="Wh/m2", range=[0, 1200], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - IP=IP(unit="Btu/ft2", range=[0, 1200 * 0.3169983306]), + color=SUN_COLORS, + IP=IP(unit="Btu/ft2", range=[0, 1200 * WHM2_TO_BTUFT2]), ) DIR_NOR_RAD = VariableInfo( col_name="dir_nor_rad", name="Direct normal radiation", unit="Wh/m2", range=[0, 1200], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - IP=IP(unit="Btu/ft2", range=[0, 1200 * 0.3169983306]), + color=SUN_COLORS, + IP=IP(unit="Btu/ft2", range=[0, 1200 * WHM2_TO_BTUFT2]), ) DIF_HOR_RAD = VariableInfo( col_name="dif_hor_rad", name="Diffuse horizontal radiation", unit="Wh/m2", range=[0, 1200], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], - IP=IP(unit="Btu/ft2", range=[0, 1200 * 0.3169983306]), + color=SUN_COLORS, + IP=IP(unit="Btu/ft2", range=[0, 1200 * WHM2_TO_BTUFT2]), ) # ==================== Lighting Related Variables ==================== @@ -229,24 +239,24 @@ class Variables: name="Global horizontal illuminance", unit="lux", range=[0, 120000], - color=["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], - IP=IP(unit="fc", range=[0, 120000 * 0.0929]), + color=LIGHT_COLORS, + IP=IP(unit="fc", range=[0, 120000 * LUX_TO_FC]), ) DIR_NOR_ILL = VariableInfo( col_name="dir_nor_ill", name="Direct normal illuminance", unit="lux", range=[0, 120000], - color=["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], - IP=IP(unit="fc", range=[0, 120000 * 0.0929]), + color=LIGHT_COLORS, + IP=IP(unit="fc", range=[0, 120000 * LUX_TO_FC]), ) DIF_HOR_ILL = VariableInfo( col_name="dif_hor_ill", name="Diffuse horizontal illuminance", unit="lux", range=[0, 120000], - color=["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"], - IP=IP(unit="fc", range=[0, 120000 * 0.0929]), + color=LIGHT_COLORS, + IP=IP(unit="fc", range=[0, 120000 * LUX_TO_FC]), ) ZLUMI = VariableInfo( @@ -271,7 +281,7 @@ class Variables: name="Wind speed", unit="m/s", range=[0, 20], - color=[cc.CET_L19[int(round(i*(len(cc.CET_L19)-1)/(9)))] for i in range(10)], + color=CET_L19_10, IP=IP(unit="fpm", range=[0, 20 * 196.85039370078738]), ) @@ -280,33 +290,21 @@ class Variables: name="Total sky cover", unit="tenths", range=[0, 10], - color=[ - "#08306b", - "#7ec9f3", - "#e6eae9", - ], + color=SKY_COVER_TRI, ) OPAQUE_SKY_COVER = VariableInfo( col_name="Oskycover", name="Opaque sky cover", unit="tenths", range=[0, 10], - color=[ - "#08306b", - "#7ec9f3", - "#e6eae9", - ], + color=SKY_COVER_TRI, ) VIS = VariableInfo( col_name="Vis", name="Visibility", unit="km", range=[0, 100], - color=[ - "#08306b", - "#7ec9f3", - "#e6eae9", - ], + color=SKY_COVER_TRI, IP=IP(unit="miles", range=[0, 100 * 0.6215]), ) @@ -316,90 +314,42 @@ class Variables: name="Apparent zenith", unit="°deg", range=[0, 180], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], + color=SUN_COLORS, ) ZENITH = VariableInfo( col_name="zenith", name="Zenith", unit="°deg", range=[0, 180], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], + color=SUN_COLORS, ) APPARENT_ELEVATION = VariableInfo( col_name="apparent_elevation", name="Apparent elevation", unit="°deg", range=[-90, 90], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], + color=SUN_COLORS, ) ELEVATION = VariableInfo( col_name="elevation", name="Elevation", unit="°deg", range=[-90, 90], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], + color=SUN_COLORS, ) AZIMUTH = VariableInfo( col_name="azimuth", name="Azimuth", unit="°deg", range=[0, 360], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], + color=SUN_COLORS, ) EQUATION_OF_TIME = VariableInfo( col_name="equation_of_time", name="Equation of time", unit="°deg", range=[-20, 20], - color=[ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", - ], + color=SUN_COLORS, ) # ==================== UTCI Comfort Related Variables ==================== @@ -408,7 +358,7 @@ class Variables: name="UTCI: Sun & Wind", unit="°C", range=[-70, 70], - color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + color=BLUE_RED_YELLOW, IP=IP(unit="°F", range=[-94, 158]), ) UTCI_NO_SUN_WIND = VariableInfo( @@ -416,7 +366,7 @@ class Variables: name="UTCI: no Sun & Wind", unit="°C", range=[-70, 70], - color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + color=BLUE_RED_YELLOW, IP=IP(unit="°F", range=[-94, 158]), ) UTCI_SUN_NO_WIND = VariableInfo( @@ -424,7 +374,7 @@ class Variables: name="UTCI: Sun & no Wind", unit="°C", range=[-70, 70], - color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + color=BLUE_RED_YELLOW, IP=IP(unit="°F", range=[-94, 158]), ) UTCI_NO_SUN_NO_WIND = VariableInfo( @@ -432,7 +382,7 @@ class Variables: name="UTCI: no Sun & no Wind", unit="°C", range=[-70, 70], - color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + color=BLUE_RED_YELLOW, IP=IP(unit="°F", range=[-94, 158]), ) @@ -441,112 +391,28 @@ class Variables: name="UTCI: Sun & Wind : categories", unit="Thermal stress", range=[-5, 4], - color=[ - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.1665, "#38429B"], - [0.1665, "#4253A4"], - [0.2775, "#4253A4"], - [0.2775, "#4B62AD"], - [0.3885, "#4B62AD"], - [0.3885, "#68B8E7"], - [0.4995, "#68B8E7"], - [0.4995, "#53B848"], - [0.6105, "#53B848"], - [0.6105, "#EE8522"], - [0.7215, "#EE8522"], - [0.7215, "#EA2C24"], - [0.8325, "#EA2C24"], - [0.8325, "#B12224"], - [0.9435, "#B12224"], - [0.9435, "#751613"], - [1.0, "#751613"], - ], + color=UTCI_CATEGORIES_SCALE, ) UTCI_NOSUN_WIND_CATEGORIES = VariableInfo( col_name="utci_noSun_Wind_categories", name="UTCI: no Sun & Wind : categories", unit="Thermal stress", range=[-5, 4], - color=[ - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.1665, "#38429B"], - [0.1665, "#4253A4"], - [0.2775, "#4253A4"], - [0.2775, "#4B62AD"], - [0.3885, "#4B62AD"], - [0.3885, "#68B8E7"], - [0.4995, "#68B8E7"], - [0.4995, "#53B848"], - [0.6105, "#53B848"], - [0.6105, "#EE8522"], - [0.7215, "#EE8522"], - [0.7215, "#EA2C24"], - [0.8325, "#EA2C24"], - [0.8325, "#B12224"], - [0.9435, "#B12224"], - [0.9435, "#751613"], - [1.0, "#751613"], - ], + color=UTCI_CATEGORIES_SCALE, ) UTCI_SUN_NOWIND_CATEGORIES = VariableInfo( col_name="utci_Sun_noWind_categories", name="UTCI: Sun & no Wind : categories", unit="Thermal stress", range=[-5, 4], - color=[ - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.1665, "#38429B"], - [0.1665, "#4253A4"], - [0.2775, "#4253A4"], - [0.2775, "#4B62AD"], - [0.3885, "#4B62AD"], - [0.3885, "#68B8E7"], - [0.4995, "#68B8E7"], - [0.4995, "#53B848"], - [0.6105, "#53B848"], - [0.6105, "#EE8522"], - [0.7215, "#EE8522"], - [0.7215, "#EA2C24"], - [0.8325, "#EA2C24"], - [0.8325, "#B12224"], - [0.9435, "#B12224"], - [0.9435, "#751613"], - [1.0, "#751613"], - ], + color=UTCI_CATEGORIES_SCALE, ) UTCI_NOSUN_NOWIND_CATEGORIES = VariableInfo( col_name="utci_noSun_noWind_categories", name="UTCI: no Sun & no Wind : categories", unit="Thermal stress", range=[-5, 4], - color=[ - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.1665, "#38429B"], - [0.1665, "#4253A4"], - [0.2775, "#4253A4"], - [0.2775, "#4B62AD"], - [0.3885, "#4B62AD"], - [0.3885, "#68B8E7"], - [0.4995, "#68B8E7"], - [0.4995, "#53B848"], - [0.6105, "#53B848"], - [0.6105, "#EE8522"], - [0.7215, "#EE8522"], - [0.7215, "#EA2C24"], - [0.8325, "#EA2C24"], - [0.8325, "#B12224"], - [0.9435, "#B12224"], - [0.9435, "#751613"], - [1.0, "#751613"], - ], + color=UTCI_CATEGORIES_SCALE, ) # ==================== Additional Meteorological Data ==================== @@ -555,7 +421,7 @@ class Variables: name="Vapor partial pressure", unit="Pa", range=[0, 5000], - color=["#ffe600", "#00c8ff", "#0000ff"], + color=DRY_HUMID, IP=IP(unit="Psi", range=[0, 5000 * 0.000145038]), ) P_SAT = VariableInfo( @@ -570,7 +436,7 @@ class Variables: name="Absolute humidity", unit="g water/kg dry air", range=[0, 0.03 * 1000], - color=["#ffe600", "#00c8ff", "#0000ff"], + color=DRY_HUMID, IP=IP(unit="lb water/klb dry air", range=[0, 0.03 * 1000]), ) T_WB = VariableInfo( @@ -578,7 +444,7 @@ class Variables: name="Wet bulb temperature", unit="°C", range=[-40, 50], - color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + color=BLUE_RED_YELLOW, IP=IP(unit="°F", range=[-40, 122]), ) T_DP = VariableInfo( @@ -586,7 +452,7 @@ class Variables: name="Dew point temperature", unit="°C", range=[-40, 50], - color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + color=BLUE_RED_YELLOW, IP=IP(unit="°F", range=[-40, 122]), ) EH = VariableInfo( @@ -594,7 +460,7 @@ class Variables: name="Enthalpy", unit="J/kg dry air", range=[0, 110000], - color=["#00b3ff", "#000082", "#ff0000", "#ffff00"], + color=BLUE_RED_YELLOW, IP=IP(unit="Btu/lb dry air", range=[0, 110000 * 0.000429923]), ) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 8477d5f3..8030bf66 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -6,7 +6,7 @@ from config import UnitSystem from pages.lib.utils import get_max_min_value import dash_bootstrap_components as dbc -from .global_scheme import month_lst, template, tight_margins +from .global_scheme import month_lst, template, tight_margins, WIND_ROSE_BINS from pages.lib.global_variables import Variables, VariableInfo from .utils import code_timer, determine_month_and_hour_filter @@ -431,7 +431,7 @@ def heatmap_with_filter( ) if var == Variables.WIND_SPEED.col_name: - spd_bins = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] + spd_bins = list(WIND_ROSE_BINS) if si_ip == UnitSystem.IP: spd_bins = convert_bins(spd_bins) fig.update_traces(zmin=0, zmax=spd_bins[-2]) @@ -499,7 +499,7 @@ def heatmap(df, var, global_local, si_ip): ) if var == Variables.WIND_SPEED.col_name: - spd_bins = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] + spd_bins = list(WIND_ROSE_BINS) if si_ip == UnitSystem.IP: spd_bins = convert_bins(spd_bins) fig.update_traces(zmin=0, zmax=spd_bins[-2]) From e8eebace3ebc735776b28b51058908d59317aada Mon Sep 17 00:00:00 2001 From: FengW01 Date: Wed, 22 Oct 2025 16:47:38 +1100 Subject: [PATCH 111/163] Remove redundancy --- pages/lib/global_scheme.py | 90 +++++++++++++++--------------------- pages/lib/template_graphs.py | 2 +- 2 files changed, 37 insertions(+), 55 deletions(-) diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index ab2ade5c..5879ce85 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,40 +1,52 @@ import plotly.io as pio -import colorcet as cc import numpy as np -from plotly.colors import sequential as pseq -from pages.lib.global_variables import Variables, VariableInfo +from pages.lib.global_variables import Variables, VariableInfo, CET_L19_10 # Colors Dictionary -blue_red_yellow = ["#00b3ff", "#000082", "#ff0000", "#ffff00"] -dry_humid = ["#ffe600", "#00c8ff", "#0000ff"] -sun_colors = [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", -] -light_colors = ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"] -bright_colors = ["#730a8c", "#0d0db3", "#0f85be", "#0f85be", "#b11421", "#fdf130"] -# Take 10 colors at equal intervals (including both ends) -wind_speed_color = [ - cc.CET_L19[int(round(i * (len(cc.CET_L19) - 1) / (9)))] for i in range(10) -] - +wind_speed_color = CET_L19_10 WIND_ROSE_BINS = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] def _stepped_colorscale_from_bins(bins, colors): - vmin, vmax = bins[0], bins[-1] + """ + Build a stepped colorscale from bin edges and colors. + + Args: + bins: sequence/list of N+1 bin edges (ascending). The last edge may be np.inf. + colors: sequence/list of N colors, one per interval. + + Returns: + List of (position, color) tuples for a Plotly colorscale with positions in [0, 1]. + """ + if len(bins) != len(colors) + 1: + raise ValueError( + f"Expected {len(colors) + 1} bin edges for {len(colors)} colors, " + f"got {len(bins)}" + ) + + if any(b2 < b1 for b1, b2 in zip(bins, bins[1:])): + raise ValueError("bins must be in non-decreasing order") + + finite_edges = [b for b in bins if np.isfinite(b)] + if not finite_edges: + raise ValueError("All bin edges are non-finite; cannot build colorscale.") + + vmin, vmax = finite_edges[0], finite_edges[-1] span = (vmax - vmin) or 1.0 + cs = [] for i, c in enumerate(colors): - left = (bins[i] - vmin) / span - right = ((bins[i + 1] if i + 1 < len(bins) else vmax) - vmin) / span + left_edge, right_edge = bins[i], bins[i + 1] + + left = 0.0 if not np.isfinite(left_edge) else (left_edge - vmin) / span + right = 1.0 if not np.isfinite(right_edge) else (right_edge - vmin) / span + + left = max(0.0, min(1.0, float(left))) + right = max(0.0, min(1.0, float(right))) + cs.append((left, c)) cs.append((right, c)) + return cs @@ -42,36 +54,6 @@ def _stepped_colorscale_from_bins(bins, colors): WIND_ROSE_BINS, wind_speed_color ) -wind_dir_color = list(pseq.Viridis) -cloud_colors = [ - "#08306b", - "#7ec9f3", - "#e6eae9", -] -utci_categories_color = [ - # Let first 10% (0.1) of the values have color rgb(0, 0, 0) - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.0555 + 0.111 * 1, "#38429B"], - [0.0555 + 0.111 * 1, "#4253A4"], - [0.0555 + 0.111 * 2, "#4253A4"], - [0.0555 + 0.111 * 2, "#4B62AD"], - [0.0555 + 0.111 * 3, "#4B62AD"], - [0.0555 + 0.111 * 3, "#68B8E7"], - [0.0555 + 0.111 * 4, "#68B8E7"], - [0.0555 + 0.111 * 4, "#53B848"], - [0.0555 + 0.111 * 5, "#53B848"], - [0.0555 + 0.111 * 5, "#EE8522"], - [0.0555 + 0.111 * 6, "#EE8522"], - [0.0555 + 0.111 * 6, "#EA2C24"], - [0.0555 + 0.111 * 7, "#EA2C24"], - [0.0555 + 0.111 * 7, "#B12224"], - [0.0555 + 0.111 * 8, "#B12224"], - [0.0555 + 0.111 * 8, "#751613"], - [1.0, "#751613"], -] - # containers container_row_center_full = "container-row row-center" container_col_center_one_of_three = ( diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 8030bf66..1fb189df 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -563,7 +563,7 @@ def wind_rose(df, title, month, hour, labels, si_ip): spd_colors = wind_speed_variable.get_color() spd_unit = wind_speed_variable.get_unit(si_ip) - spd_bins = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] + spd_bins = WIND_ROSE_BINS if si_ip == UnitSystem.IP: spd_bins = convert_bins(spd_bins) From f45b6a0bd056907f9f51872cb4add5e3df592c1f Mon Sep 17 00:00:00 2001 From: FengW01 Date: Fri, 24 Oct 2025 15:16:48 +1100 Subject: [PATCH 112/163] refactor(colors): consolidate shared palettes & unit conversions into Enum and drop colorcet in favor of hex color lists --- Pipfile | 1 - Pipfile.lock | 11 +- pages/lib/global_scheme.py | 6 +- pages/lib/global_variables.py | 194 ++++++++++++++++++---------------- requirements.txt | 1 - 5 files changed, 106 insertions(+), 107 deletions(-) diff --git a/Pipfile b/Pipfile index 210142c0..0c01f3de 100644 --- a/Pipfile +++ b/Pipfile @@ -16,7 +16,6 @@ pandas = "==2.2.0" numpy = "==1.26.3" dash-iconify = "*" scipy = "==1.12.0" -colorcet = "<4,>=3.0.1" [dev-packages] cleanpy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index ce60fcad..8e475b6d 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "79b1a6efc301bcc68eb75b61790df1ac8051751236f6b505086c26572d4d3458" + "sha256": "4f09fe1d5fd82b15503fd3b0c1606e5e6139df983488fdc78c72555daf01e167" }, "pipfile-spec": 6, "requires": { @@ -133,15 +133,6 @@ "markers": "python_version >= '3.10'", "version": "==8.2.1" }, - "colorcet": { - "hashes": [ - "sha256:2921b3cd81a2288aaf2d63dbc0ce3c26dcd882e8c389cc505d6886bf7aa9a4eb", - "sha256:2a7d59cc8d0f7938eeedd08aad3152b5319b4ba3bcb7a612398cc17a384cb296" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==3.1.0" - }, "dash": { "hashes": [ "sha256:4c1819588d83bed2cbcf5807daa5c2380c8c85789a6935a733f018f04ad8a6a2", diff --git a/pages/lib/global_scheme.py b/pages/lib/global_scheme.py index 5879ce85..8b93053c 100644 --- a/pages/lib/global_scheme.py +++ b/pages/lib/global_scheme.py @@ -1,9 +1,7 @@ import plotly.io as pio import numpy as np -from pages.lib.global_variables import Variables, VariableInfo, CET_L19_10 +from pages.lib.global_variables import Variables, VariableInfo -# Colors Dictionary -wind_speed_color = CET_L19_10 WIND_ROSE_BINS = [-1, 0.5, 1.5, 3.3, 5.5, 7.9, 10.7, 13.8, 17.1, 20.7, np.inf] @@ -51,7 +49,7 @@ def _stepped_colorscale_from_bins(bins, colors): wind_speed_colorscale_rose = _stepped_colorscale_from_bins( - WIND_ROSE_BINS, wind_speed_color + WIND_ROSE_BINS, Variables.WIND_SPEED.color ) # containers diff --git a/pages/lib/global_variables.py b/pages/lib/global_variables.py index 5a101cf0..bcc63c5d 100644 --- a/pages/lib/global_variables.py +++ b/pages/lib/global_variables.py @@ -1,57 +1,55 @@ from dataclasses import dataclass from typing import Optional, List, Any +from enum import Enum from config import UnitSystem -import colorcet as cc from plotly.colors import sequential as pseq + # ---------- Shared palettes & conversion constants ---------- -BLUE_RED_YELLOW = ["#00b3ff", "#000082", "#ff0000", "#ffff00"] -DRY_HUMID = ["#ffe600", "#00c8ff", "#0000ff"] -SUN_COLORS = [ - "#293a59", - "#960c2c", - "#ff0000", - "#ff7b00", - "#fffc00", - "#ffff7b", - "#ffffff", -] -LIGHT_COLORS = ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"] -SKY_COVER_TRI = ["#08306b", "#7ec9f3", "#e6eae9"] - -# Evenly sample 10 colors (including both endpoints) from CET_L19 -CET_L19_10 = [ - cc.CET_L19[int(round(i * (len(cc.CET_L19) - 1) / (9)))] for i in range(10) -] - -# Unit conversions -PA_TO_PSI = 0.000145038 -WHM2_TO_BTUFT2 = 0.3169983306 -LUX_TO_FC = 0.0929 - -# UTCI category colorscale -UTCI_CATEGORIES_SCALE = [ - [0, "#2B2977"], - [0.0555, "#2B2977"], - [0.0555, "#38429B"], - [0.1665, "#38429B"], - [0.1665, "#4253A4"], - [0.2775, "#4253A4"], - [0.2775, "#4B62AD"], - [0.3885, "#4B62AD"], - [0.3885, "#68B8E7"], - [0.4995, "#68B8E7"], - [0.4995, "#53B848"], - [0.6105, "#53B848"], - [0.6105, "#EE8522"], - [0.7215, "#EE8522"], - [0.7215, "#EA2C24"], - [0.8325, "#EA2C24"], - [0.8325, "#B12224"], - [0.9435, "#B12224"], - [0.9435, "#751613"], - [1.0, "#751613"], -] +class Common(Enum): + # Palettes + BLUE_RED_YELLOW = ["#00b3ff", "#000082", "#ff0000", "#ffff00"] + DRY_HUMID = ["#ffe600", "#00c8ff", "#0000ff"] + SUN_COLORS = [ + "#293a59", + "#960c2c", + "#ff0000", + "#ff7b00", + "#fffc00", + "#ffff7b", + "#ffffff", + ] + LIGHT_COLORS = ["#4d6daa", "#a0beed", "#f1e969", "#eb7d05", "#d81600"] + SKY_COVER_TRI = ["#08306b", "#7ec9f3", "#e6eae9"] + + # Unit conversions + PA_TO_PSI = 0.000145038 + WHM2_TO_BTUFT2 = 0.3169983306 + LUX_TO_FC = 0.0929 + + # UTCI category colorscale + UTCI_CATEGORIES_SCALE = [ + [0, "#2B2977"], + [0.0555, "#2B2977"], + [0.0555, "#38429B"], + [0.1665, "#38429B"], + [0.1665, "#4253A4"], + [0.2775, "#4253A4"], + [0.2775, "#4B62AD"], + [0.3885, "#4B62AD"], + [0.3885, "#68B8E7"], + [0.4995, "#68B8E7"], + [0.4995, "#53B848"], + [0.6105, "#53B848"], + [0.6105, "#EE8522"], + [0.7215, "#EE8522"], + [0.7215, "#EA2C24"], + [0.8325, "#EA2C24"], + [0.8325, "#B12224"], + [0.9435, "#B12224"], + [0.9435, "#751613"], + [1.0, "#751613"], + ] @dataclass @@ -152,7 +150,7 @@ class Variables: name="Dry bulb temperature", unit="°C", range=[-40, 50], - color=BLUE_RED_YELLOW, + color=Common.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-40, 122]), ) DPT = VariableInfo( @@ -160,7 +158,7 @@ class Variables: name="Dew point temperature", unit="°C", range=[-50, 35], - color=BLUE_RED_YELLOW, + color=Common.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-58, 95]), ) RH = VariableInfo( @@ -168,7 +166,7 @@ class Variables: name="Relative humidity", unit="%", range=[0, 100], - color=DRY_HUMID, + color=Common.DRY_HUMID.value, ) P_ATM = VariableInfo( @@ -188,7 +186,10 @@ class Variables: "#cc0000", "#ffaa00", ], - IP=IP(unit="Psi", range=[95000 * PA_TO_PSI, 105000 * PA_TO_PSI]), + IP=IP( + unit="Psi", + range=[95000 * Common.PA_TO_PSI.value, 105000 * Common.PA_TO_PSI.value], + ), ) # ==================== Radiation Related Variables ==================== @@ -197,40 +198,40 @@ class Variables: name="Extraterrestrial horizontal irradiation", unit="Wh/m2", range=[0, 1200], - color=SUN_COLORS, - IP=IP(unit="Btu/ft2", range=[0, 1200 * WHM2_TO_BTUFT2]), + color=Common.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 1200 * Common.WHM2_TO_BTUFT2.value]), ) HOR_IR_RAD = VariableInfo( col_name="hor_ir_rad", name="Horizontal infrared radiation", unit="Wh/m2", range=[0, 500], - color=SUN_COLORS, - IP=IP(unit="Btu/ft2", range=[0, 500 * WHM2_TO_BTUFT2]), + color=Common.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 500 * Common.WHM2_TO_BTUFT2.value]), ) GLOB_HOR_RAD = VariableInfo( col_name="glob_hor_rad", name="Global horizontal radiation", unit="Wh/m2", range=[0, 1200], - color=SUN_COLORS, - IP=IP(unit="Btu/ft2", range=[0, 1200 * WHM2_TO_BTUFT2]), + color=Common.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 1200 * Common.WHM2_TO_BTUFT2.value]), ) DIR_NOR_RAD = VariableInfo( col_name="dir_nor_rad", name="Direct normal radiation", unit="Wh/m2", range=[0, 1200], - color=SUN_COLORS, - IP=IP(unit="Btu/ft2", range=[0, 1200 * WHM2_TO_BTUFT2]), + color=Common.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 1200 * Common.WHM2_TO_BTUFT2.value]), ) DIF_HOR_RAD = VariableInfo( col_name="dif_hor_rad", name="Diffuse horizontal radiation", unit="Wh/m2", range=[0, 1200], - color=SUN_COLORS, - IP=IP(unit="Btu/ft2", range=[0, 1200 * WHM2_TO_BTUFT2]), + color=Common.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 1200 * Common.WHM2_TO_BTUFT2.value]), ) # ==================== Lighting Related Variables ==================== @@ -239,24 +240,24 @@ class Variables: name="Global horizontal illuminance", unit="lux", range=[0, 120000], - color=LIGHT_COLORS, - IP=IP(unit="fc", range=[0, 120000 * LUX_TO_FC]), + color=Common.LIGHT_COLORS.value, + IP=IP(unit="fc", range=[0, 120000 * Common.LUX_TO_FC.value]), ) DIR_NOR_ILL = VariableInfo( col_name="dir_nor_ill", name="Direct normal illuminance", unit="lux", range=[0, 120000], - color=LIGHT_COLORS, - IP=IP(unit="fc", range=[0, 120000 * LUX_TO_FC]), + color=Common.LIGHT_COLORS.value, + IP=IP(unit="fc", range=[0, 120000 * Common.LUX_TO_FC.value]), ) DIF_HOR_ILL = VariableInfo( col_name="dif_hor_ill", name="Diffuse horizontal illuminance", unit="lux", range=[0, 120000], - color=LIGHT_COLORS, - IP=IP(unit="fc", range=[0, 120000 * LUX_TO_FC]), + color=Common.LIGHT_COLORS.value, + IP=IP(unit="fc", range=[0, 120000 * Common.LUX_TO_FC.value]), ) ZLUMI = VariableInfo( @@ -281,7 +282,18 @@ class Variables: name="Wind speed", unit="m/s", range=[0, 20], - color=CET_L19_10, + color=[ + "#feffff", + "#dcf0fc", + "#c2defe", + "#b8cafd", + "#beb1f5", + "#d093df", + "#e272bb", + "#eb4f8b", + "#e52d51", + "#d0210e", + ], IP=IP(unit="fpm", range=[0, 20 * 196.85039370078738]), ) @@ -290,21 +302,21 @@ class Variables: name="Total sky cover", unit="tenths", range=[0, 10], - color=SKY_COVER_TRI, + color=Common.SKY_COVER_TRI.value, ) OPAQUE_SKY_COVER = VariableInfo( col_name="Oskycover", name="Opaque sky cover", unit="tenths", range=[0, 10], - color=SKY_COVER_TRI, + color=Common.SKY_COVER_TRI.value, ) VIS = VariableInfo( col_name="Vis", name="Visibility", unit="km", range=[0, 100], - color=SKY_COVER_TRI, + color=Common.SKY_COVER_TRI.value, IP=IP(unit="miles", range=[0, 100 * 0.6215]), ) @@ -314,42 +326,42 @@ class Variables: name="Apparent zenith", unit="°deg", range=[0, 180], - color=SUN_COLORS, + color=Common.SUN_COLORS.value, ) ZENITH = VariableInfo( col_name="zenith", name="Zenith", unit="°deg", range=[0, 180], - color=SUN_COLORS, + color=Common.SUN_COLORS.value, ) APPARENT_ELEVATION = VariableInfo( col_name="apparent_elevation", name="Apparent elevation", unit="°deg", range=[-90, 90], - color=SUN_COLORS, + color=Common.SUN_COLORS.value, ) ELEVATION = VariableInfo( col_name="elevation", name="Elevation", unit="°deg", range=[-90, 90], - color=SUN_COLORS, + color=Common.SUN_COLORS.value, ) AZIMUTH = VariableInfo( col_name="azimuth", name="Azimuth", unit="°deg", range=[0, 360], - color=SUN_COLORS, + color=Common.SUN_COLORS.value, ) EQUATION_OF_TIME = VariableInfo( col_name="equation_of_time", name="Equation of time", unit="°deg", range=[-20, 20], - color=SUN_COLORS, + color=Common.SUN_COLORS.value, ) # ==================== UTCI Comfort Related Variables ==================== @@ -358,7 +370,7 @@ class Variables: name="UTCI: Sun & Wind", unit="°C", range=[-70, 70], - color=BLUE_RED_YELLOW, + color=Common.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-94, 158]), ) UTCI_NO_SUN_WIND = VariableInfo( @@ -366,7 +378,7 @@ class Variables: name="UTCI: no Sun & Wind", unit="°C", range=[-70, 70], - color=BLUE_RED_YELLOW, + color=Common.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-94, 158]), ) UTCI_SUN_NO_WIND = VariableInfo( @@ -374,7 +386,7 @@ class Variables: name="UTCI: Sun & no Wind", unit="°C", range=[-70, 70], - color=BLUE_RED_YELLOW, + color=Common.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-94, 158]), ) UTCI_NO_SUN_NO_WIND = VariableInfo( @@ -382,7 +394,7 @@ class Variables: name="UTCI: no Sun & no Wind", unit="°C", range=[-70, 70], - color=BLUE_RED_YELLOW, + color=Common.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-94, 158]), ) @@ -391,28 +403,28 @@ class Variables: name="UTCI: Sun & Wind : categories", unit="Thermal stress", range=[-5, 4], - color=UTCI_CATEGORIES_SCALE, + color=Common.UTCI_CATEGORIES_SCALE.value, ) UTCI_NOSUN_WIND_CATEGORIES = VariableInfo( col_name="utci_noSun_Wind_categories", name="UTCI: no Sun & Wind : categories", unit="Thermal stress", range=[-5, 4], - color=UTCI_CATEGORIES_SCALE, + color=Common.UTCI_CATEGORIES_SCALE.value, ) UTCI_SUN_NOWIND_CATEGORIES = VariableInfo( col_name="utci_Sun_noWind_categories", name="UTCI: Sun & no Wind : categories", unit="Thermal stress", range=[-5, 4], - color=UTCI_CATEGORIES_SCALE, + color=Common.UTCI_CATEGORIES_SCALE.value, ) UTCI_NOSUN_NOWIND_CATEGORIES = VariableInfo( col_name="utci_noSun_noWind_categories", name="UTCI: no Sun & no Wind : categories", unit="Thermal stress", range=[-5, 4], - color=UTCI_CATEGORIES_SCALE, + color=Common.UTCI_CATEGORIES_SCALE.value, ) # ==================== Additional Meteorological Data ==================== @@ -421,7 +433,7 @@ class Variables: name="Vapor partial pressure", unit="Pa", range=[0, 5000], - color=DRY_HUMID, + color=Common.DRY_HUMID.value, IP=IP(unit="Psi", range=[0, 5000 * 0.000145038]), ) P_SAT = VariableInfo( @@ -436,7 +448,7 @@ class Variables: name="Absolute humidity", unit="g water/kg dry air", range=[0, 0.03 * 1000], - color=DRY_HUMID, + color=Common.DRY_HUMID.value, IP=IP(unit="lb water/klb dry air", range=[0, 0.03 * 1000]), ) T_WB = VariableInfo( @@ -444,7 +456,7 @@ class Variables: name="Wet bulb temperature", unit="°C", range=[-40, 50], - color=BLUE_RED_YELLOW, + color=Common.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-40, 122]), ) T_DP = VariableInfo( @@ -452,7 +464,7 @@ class Variables: name="Dew point temperature", unit="°C", range=[-40, 50], - color=BLUE_RED_YELLOW, + color=Common.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-40, 122]), ) EH = VariableInfo( @@ -460,7 +472,7 @@ class Variables: name="Enthalpy", unit="J/kg dry air", range=[0, 110000], - color=BLUE_RED_YELLOW, + color=Common.BLUE_RED_YELLOW.value, IP=IP(unit="Btu/lb dry air", range=[0, 110000 * 0.000429923]), ) diff --git a/requirements.txt b/requirements.txt index aca379ed..14cdb68c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,6 @@ certifi==2025.7.14 charset-normalizer==3.4.2 cleanpy==0.5.1 click==8.2.1 -colorcet>=3.0.1,<4 dash==3.2.0 dash-bootstrap-components==1.2.0 dash-core-components==2.0.0 From 077d18c2af92800e911303400ee02b850e37b463 Mon Sep 17 00:00:00 2001 From: Federico Tartarini Date: Mon, 27 Oct 2025 14:34:09 +1100 Subject: [PATCH 113/163] refactor(global_variables): rename Common to ColorPalettes and update references --- pages/lib/global_variables.py | 89 +++++++++++++++++------------------ 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/pages/lib/global_variables.py b/pages/lib/global_variables.py index bcc63c5d..a761d95f 100644 --- a/pages/lib/global_variables.py +++ b/pages/lib/global_variables.py @@ -5,8 +5,7 @@ from plotly.colors import sequential as pseq -# ---------- Shared palettes & conversion constants ---------- -class Common(Enum): +class ColorPalettes(Enum): # Palettes BLUE_RED_YELLOW = ["#00b3ff", "#000082", "#ff0000", "#ffff00"] DRY_HUMID = ["#ffe600", "#00c8ff", "#0000ff"] @@ -24,7 +23,7 @@ class Common(Enum): # Unit conversions PA_TO_PSI = 0.000145038 - WHM2_TO_BTUFT2 = 0.3169983306 + WHM2_TO_BTU_FT2 = 0.3169983306 LUX_TO_FC = 0.0929 # UTCI category colorscale @@ -150,7 +149,7 @@ class Variables: name="Dry bulb temperature", unit="°C", range=[-40, 50], - color=Common.BLUE_RED_YELLOW.value, + color=ColorPalettes.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-40, 122]), ) DPT = VariableInfo( @@ -158,7 +157,7 @@ class Variables: name="Dew point temperature", unit="°C", range=[-50, 35], - color=Common.BLUE_RED_YELLOW.value, + color=ColorPalettes.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-58, 95]), ) RH = VariableInfo( @@ -166,7 +165,7 @@ class Variables: name="Relative humidity", unit="%", range=[0, 100], - color=Common.DRY_HUMID.value, + color=ColorPalettes.DRY_HUMID.value, ) P_ATM = VariableInfo( @@ -188,7 +187,7 @@ class Variables: ], IP=IP( unit="Psi", - range=[95000 * Common.PA_TO_PSI.value, 105000 * Common.PA_TO_PSI.value], + range=[95000 * ColorPalettes.PA_TO_PSI.value, 105000 * ColorPalettes.PA_TO_PSI.value], ), ) @@ -198,40 +197,40 @@ class Variables: name="Extraterrestrial horizontal irradiation", unit="Wh/m2", range=[0, 1200], - color=Common.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 1200 * Common.WHM2_TO_BTUFT2.value]), + color=ColorPalettes.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value]), ) HOR_IR_RAD = VariableInfo( col_name="hor_ir_rad", name="Horizontal infrared radiation", unit="Wh/m2", range=[0, 500], - color=Common.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 500 * Common.WHM2_TO_BTUFT2.value]), + color=ColorPalettes.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 500 * ColorPalettes.WHM2_TO_BTU_FT2.value]), ) GLOB_HOR_RAD = VariableInfo( col_name="glob_hor_rad", name="Global horizontal radiation", unit="Wh/m2", range=[0, 1200], - color=Common.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 1200 * Common.WHM2_TO_BTUFT2.value]), + color=ColorPalettes.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value]), ) DIR_NOR_RAD = VariableInfo( col_name="dir_nor_rad", name="Direct normal radiation", unit="Wh/m2", range=[0, 1200], - color=Common.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 1200 * Common.WHM2_TO_BTUFT2.value]), + color=ColorPalettes.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value]), ) DIF_HOR_RAD = VariableInfo( col_name="dif_hor_rad", name="Diffuse horizontal radiation", unit="Wh/m2", range=[0, 1200], - color=Common.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 1200 * Common.WHM2_TO_BTUFT2.value]), + color=ColorPalettes.SUN_COLORS.value, + IP=IP(unit="Btu/ft2", range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value]), ) # ==================== Lighting Related Variables ==================== @@ -240,24 +239,24 @@ class Variables: name="Global horizontal illuminance", unit="lux", range=[0, 120000], - color=Common.LIGHT_COLORS.value, - IP=IP(unit="fc", range=[0, 120000 * Common.LUX_TO_FC.value]), + color=ColorPalettes.LIGHT_COLORS.value, + IP=IP(unit="fc", range=[0, 120000 * ColorPalettes.LUX_TO_FC.value]), ) DIR_NOR_ILL = VariableInfo( col_name="dir_nor_ill", name="Direct normal illuminance", unit="lux", range=[0, 120000], - color=Common.LIGHT_COLORS.value, - IP=IP(unit="fc", range=[0, 120000 * Common.LUX_TO_FC.value]), + color=ColorPalettes.LIGHT_COLORS.value, + IP=IP(unit="fc", range=[0, 120000 * ColorPalettes.LUX_TO_FC.value]), ) DIF_HOR_ILL = VariableInfo( col_name="dif_hor_ill", name="Diffuse horizontal illuminance", unit="lux", range=[0, 120000], - color=Common.LIGHT_COLORS.value, - IP=IP(unit="fc", range=[0, 120000 * Common.LUX_TO_FC.value]), + color=ColorPalettes.LIGHT_COLORS.value, + IP=IP(unit="fc", range=[0, 120000 * ColorPalettes.LUX_TO_FC.value]), ) ZLUMI = VariableInfo( @@ -302,21 +301,21 @@ class Variables: name="Total sky cover", unit="tenths", range=[0, 10], - color=Common.SKY_COVER_TRI.value, + color=ColorPalettes.SKY_COVER_TRI.value, ) OPAQUE_SKY_COVER = VariableInfo( col_name="Oskycover", name="Opaque sky cover", unit="tenths", range=[0, 10], - color=Common.SKY_COVER_TRI.value, + color=ColorPalettes.SKY_COVER_TRI.value, ) VIS = VariableInfo( col_name="Vis", name="Visibility", unit="km", range=[0, 100], - color=Common.SKY_COVER_TRI.value, + color=ColorPalettes.SKY_COVER_TRI.value, IP=IP(unit="miles", range=[0, 100 * 0.6215]), ) @@ -326,42 +325,42 @@ class Variables: name="Apparent zenith", unit="°deg", range=[0, 180], - color=Common.SUN_COLORS.value, + color=ColorPalettes.SUN_COLORS.value, ) ZENITH = VariableInfo( col_name="zenith", name="Zenith", unit="°deg", range=[0, 180], - color=Common.SUN_COLORS.value, + color=ColorPalettes.SUN_COLORS.value, ) APPARENT_ELEVATION = VariableInfo( col_name="apparent_elevation", name="Apparent elevation", unit="°deg", range=[-90, 90], - color=Common.SUN_COLORS.value, + color=ColorPalettes.SUN_COLORS.value, ) ELEVATION = VariableInfo( col_name="elevation", name="Elevation", unit="°deg", range=[-90, 90], - color=Common.SUN_COLORS.value, + color=ColorPalettes.SUN_COLORS.value, ) AZIMUTH = VariableInfo( col_name="azimuth", name="Azimuth", unit="°deg", range=[0, 360], - color=Common.SUN_COLORS.value, + color=ColorPalettes.SUN_COLORS.value, ) EQUATION_OF_TIME = VariableInfo( col_name="equation_of_time", name="Equation of time", unit="°deg", range=[-20, 20], - color=Common.SUN_COLORS.value, + color=ColorPalettes.SUN_COLORS.value, ) # ==================== UTCI Comfort Related Variables ==================== @@ -370,7 +369,7 @@ class Variables: name="UTCI: Sun & Wind", unit="°C", range=[-70, 70], - color=Common.BLUE_RED_YELLOW.value, + color=ColorPalettes.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-94, 158]), ) UTCI_NO_SUN_WIND = VariableInfo( @@ -378,7 +377,7 @@ class Variables: name="UTCI: no Sun & Wind", unit="°C", range=[-70, 70], - color=Common.BLUE_RED_YELLOW.value, + color=ColorPalettes.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-94, 158]), ) UTCI_SUN_NO_WIND = VariableInfo( @@ -386,7 +385,7 @@ class Variables: name="UTCI: Sun & no Wind", unit="°C", range=[-70, 70], - color=Common.BLUE_RED_YELLOW.value, + color=ColorPalettes.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-94, 158]), ) UTCI_NO_SUN_NO_WIND = VariableInfo( @@ -394,7 +393,7 @@ class Variables: name="UTCI: no Sun & no Wind", unit="°C", range=[-70, 70], - color=Common.BLUE_RED_YELLOW.value, + color=ColorPalettes.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-94, 158]), ) @@ -403,28 +402,28 @@ class Variables: name="UTCI: Sun & Wind : categories", unit="Thermal stress", range=[-5, 4], - color=Common.UTCI_CATEGORIES_SCALE.value, + color=ColorPalettes.UTCI_CATEGORIES_SCALE.value, ) UTCI_NOSUN_WIND_CATEGORIES = VariableInfo( col_name="utci_noSun_Wind_categories", name="UTCI: no Sun & Wind : categories", unit="Thermal stress", range=[-5, 4], - color=Common.UTCI_CATEGORIES_SCALE.value, + color=ColorPalettes.UTCI_CATEGORIES_SCALE.value, ) UTCI_SUN_NOWIND_CATEGORIES = VariableInfo( col_name="utci_Sun_noWind_categories", name="UTCI: Sun & no Wind : categories", unit="Thermal stress", range=[-5, 4], - color=Common.UTCI_CATEGORIES_SCALE.value, + color=ColorPalettes.UTCI_CATEGORIES_SCALE.value, ) UTCI_NOSUN_NOWIND_CATEGORIES = VariableInfo( col_name="utci_noSun_noWind_categories", name="UTCI: no Sun & no Wind : categories", unit="Thermal stress", range=[-5, 4], - color=Common.UTCI_CATEGORIES_SCALE.value, + color=ColorPalettes.UTCI_CATEGORIES_SCALE.value, ) # ==================== Additional Meteorological Data ==================== @@ -433,7 +432,7 @@ class Variables: name="Vapor partial pressure", unit="Pa", range=[0, 5000], - color=Common.DRY_HUMID.value, + color=ColorPalettes.DRY_HUMID.value, IP=IP(unit="Psi", range=[0, 5000 * 0.000145038]), ) P_SAT = VariableInfo( @@ -448,7 +447,7 @@ class Variables: name="Absolute humidity", unit="g water/kg dry air", range=[0, 0.03 * 1000], - color=Common.DRY_HUMID.value, + color=ColorPalettes.DRY_HUMID.value, IP=IP(unit="lb water/klb dry air", range=[0, 0.03 * 1000]), ) T_WB = VariableInfo( @@ -456,7 +455,7 @@ class Variables: name="Wet bulb temperature", unit="°C", range=[-40, 50], - color=Common.BLUE_RED_YELLOW.value, + color=ColorPalettes.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-40, 122]), ) T_DP = VariableInfo( @@ -464,7 +463,7 @@ class Variables: name="Dew point temperature", unit="°C", range=[-40, 50], - color=Common.BLUE_RED_YELLOW.value, + color=ColorPalettes.BLUE_RED_YELLOW.value, IP=IP(unit="°F", range=[-40, 122]), ) EH = VariableInfo( @@ -472,7 +471,7 @@ class Variables: name="Enthalpy", unit="J/kg dry air", range=[0, 110000], - color=Common.BLUE_RED_YELLOW.value, + color=ColorPalettes.BLUE_RED_YELLOW.value, IP=IP(unit="Btu/lb dry air", range=[0, 110000 * 0.000429923]), ) From b4858981558baac0c73c1f6ec1c22c34bee265b6 Mon Sep 17 00:00:00 2001 From: Federico Tartarini Date: Mon, 27 Oct 2025 14:36:25 +1100 Subject: [PATCH 114/163] chore(Dockerfile): enhance image setup with pipenv and improve logging --- Dockerfile | 23 +++++++----- docs/contributing/contributing.md | 4 +-- pages/lib/extract_df.py | 4 +-- requirements.txt | 59 ------------------------------- 4 files changed, 17 insertions(+), 73 deletions(-) delete mode 100644 requirements.txt diff --git a/Dockerfile b/Dockerfile index 03e5279c..9abf256d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,18 +2,25 @@ # https://hub.docker.com/_/python FROM python:3.11-slim +# Allow statements and log messages to immediately appear in the Knative logs +ENV PYTHONUNBUFFERED True + RUN apt-get update \ -&& apt-get install gcc -y \ -&& apt-get clean + && apt-get install --no-install-recommends -y gcc \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* -ENV APP_HOME /app -WORKDIR $APP_HOME +# Install pipenv +RUN python -m pip install --upgrade pip \ + && python -m pip install --no-cache-dir "pipenv>=2024.0,<2026.0" +# Set working directory +WORKDIR /app -COPY . ./ +# Copy Pipfile and Pipfile.lock +COPY Pipfile Pipfile.lock ./ -# Install production dependencies. -RUN pip install --upgrade pip -RUN pip install -r requirements.txt +# Install dependencies +RUN pipenv sync --deploy --system EXPOSE 8080 diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 3018a69f..103d52f0 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -115,9 +115,7 @@ Before submitting a Pull Request, please make sure: - You have installed project dependencies: ```bash -npm install - -pipenv install -r requirements.txt +pipenv sync ``` From the root directory, run: diff --git a/pages/lib/extract_df.py b/pages/lib/extract_df.py index ded1c783..75b8e06e 100644 --- a/pages/lib/extract_df.py +++ b/pages/lib/extract_df.py @@ -444,10 +444,8 @@ def create_df(lst, file_name): def convert_SI_to_IP(df: pd.DataFrame, name: str) -> None: """Convert SI to IP based on column name.""" if name not in df.columns: - print( - f"[convert_SI_to_IP] Column '{name}' not found in DataFrame. Skipping conversion." - ) return + match name: case ( Variables.DBT.col_name diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 14cdb68c..00000000 --- a/requirements.txt +++ /dev/null @@ -1,59 +0,0 @@ -ansi2html==1.9.2 -black==25.1.0 -blinker==1.9.0 -bump2version==1.0.1 -cachelib==0.9.0 -certifi==2025.7.14 -charset-normalizer==3.4.2 -cleanpy==0.5.1 -click==8.2.1 -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==2.2.1 -dash-table==5.0.0 -dataclass-wizard==0.22.3 -EditorConfig==0.17.1 -Flask==2.3.3 -Flask-Caching==2.0.2 -h5py==3.14.0 -idna==3.10 -importlib_metadata==8.7.0 -iniconfig==2.1.0 -itsdangerous==2.2.0 -Jinja2==3.1.6 -jsbeautifier==1.15.4 -llvmlite==0.44.0 -MarkupSafe==3.0.2 -more-itertools==9.1.0 -mypy_extensions==1.1.0 -narwhals==2.0.1 -nest-asyncio==1.6.0 -numba==0.61.2 -numpy==1.26.3 -packaging==25.0 -pandas==2.2.0 -pathspec==0.12.1 -platformdirs==4.3.8 -plotly==5.18.0 -pluggy==1.6.0 -pvlib==0.9.1 -Pygments==2.19.2 -pytest==8.4.1 -pythermalcomfort==2.9.1 -python-dateutil==2.9.0.post0 -pytz==2025.2 -requests==2.32.4 -retrying==1.4.1 -ruff==0.12.7 -scipy==1.12.0 -six==1.17.0 -tenacity==9.1.2 -typing_extensions==4.14.1 -tzdata==2025.2 -urllib3==2.5.0 -Werkzeug==3.0.6 -zipp==3.23.0 From 4f9a69ba913603befc1592df9b22249ed81d5654 Mon Sep 17 00:00:00 2001 From: Federico Tartarini Date: Mon, 27 Oct 2025 14:38:22 +1100 Subject: [PATCH 115/163] lint(global_variables): ruff format --- pages/lib/global_variables.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/pages/lib/global_variables.py b/pages/lib/global_variables.py index a761d95f..b2a3a177 100644 --- a/pages/lib/global_variables.py +++ b/pages/lib/global_variables.py @@ -187,7 +187,10 @@ class Variables: ], IP=IP( unit="Psi", - range=[95000 * ColorPalettes.PA_TO_PSI.value, 105000 * ColorPalettes.PA_TO_PSI.value], + range=[ + 95000 * ColorPalettes.PA_TO_PSI.value, + 105000 * ColorPalettes.PA_TO_PSI.value, + ], ), ) @@ -198,7 +201,10 @@ class Variables: unit="Wh/m2", range=[0, 1200], color=ColorPalettes.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value]), + IP=IP( + unit="Btu/ft2", + range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value], + ), ) HOR_IR_RAD = VariableInfo( col_name="hor_ir_rad", @@ -206,7 +212,10 @@ class Variables: unit="Wh/m2", range=[0, 500], color=ColorPalettes.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 500 * ColorPalettes.WHM2_TO_BTU_FT2.value]), + IP=IP( + unit="Btu/ft2", + range=[0, 500 * ColorPalettes.WHM2_TO_BTU_FT2.value], + ), ) GLOB_HOR_RAD = VariableInfo( col_name="glob_hor_rad", @@ -214,7 +223,10 @@ class Variables: unit="Wh/m2", range=[0, 1200], color=ColorPalettes.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value]), + IP=IP( + unit="Btu/ft2", + range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value], + ), ) DIR_NOR_RAD = VariableInfo( col_name="dir_nor_rad", @@ -222,7 +234,10 @@ class Variables: unit="Wh/m2", range=[0, 1200], color=ColorPalettes.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value]), + IP=IP( + unit="Btu/ft2", + range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value], + ), ) DIF_HOR_RAD = VariableInfo( col_name="dif_hor_rad", @@ -230,7 +245,10 @@ class Variables: unit="Wh/m2", range=[0, 1200], color=ColorPalettes.SUN_COLORS.value, - IP=IP(unit="Btu/ft2", range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value]), + IP=IP( + unit="Btu/ft2", + range=[0, 1200 * ColorPalettes.WHM2_TO_BTU_FT2.value], + ), ) # ==================== Lighting Related Variables ==================== From 3226d4e950b3cd1be796323890ac1095833de0aa Mon Sep 17 00:00:00 2001 From: yluo0664 Date: Mon, 27 Oct 2025 21:14:12 +1100 Subject: [PATCH 116/163] feat: Move the Apply filter of Month and Hour to the sidebar - Added Global filter UI - Removed the original Apply filter in the pages - Added grey fill to the heatmap --- pages/explorer.py | 324 ++++++++---------------------- pages/lib/charts_data_explorer.py | 65 +++++- pages/lib/charts_sun.py | 25 ++- pages/lib/global_element_ids.py | 53 +++-- pages/lib/layout.py | 252 ++++++++++++++++++++++- pages/lib/template_graphs.py | 226 ++++++++++++++++----- pages/lib/utils.py | 8 +- pages/natural_ventilation.py | 128 ++++-------- pages/outdoor.py | 189 +++++++++-------- pages/psy-chart.py | 93 +++------ pages/select.py | 39 +++- pages/summary.py | 10 +- pages/sun.py | 65 ++++-- pages/t_rh.py | 65 ++++-- pages/wind.py | 212 +++++-------------- tests/node/cypress/e2e/spec.cy.js | 4 +- 16 files changed, 971 insertions(+), 787 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index 2614d108..62251757 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -116,63 +116,7 @@ def section_one(): w="33%", children=dmc.Stack( children=[ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.SEC1_TIME_FILTER_INPUT, - color="blue", - ), - dmc.Group( - children=[ - dmc.Title("Month Range", order=5), - dmc.Stack( - flex=1, - children=dcc.RangeSlider( - id=ElementIds.SEC1_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "top", - }, - allowCross=False, - ), - ), - dcc.Checklist( - id=ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, - options=[{"label": "Invert", "value": "invert"}], - value=[], - ), - ], - ), - dmc.Group( - children=[ - dmc.Title("Hour Range", order=5), - dmc.Stack( - flex=1, - children=dcc.RangeSlider( - id=ElementIds.SEC1_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - allowCross=False, - ), - ), - dcc.Checklist( - id=ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, - options=[{"label": "Invert", "value": "invert"}], - value=[], - ), - ], - ), + # Month and hour filter moved to global sidebar ], ), ) @@ -193,7 +137,7 @@ def section_two_inputs(): id_button=IdButtons.CUSTOM_HEATMAP_CHART_LABEL, ), dmc.SimpleGrid( - cols=3, + cols=2, spacing="md", children=[ dmc.Group( @@ -210,69 +154,6 @@ def section_two_inputs(): ], align="flex-start", ), - dmc.Stack( - [ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.SEC2_TIME_FILTER_INPUT, - color="blue", - ), - dmc.Group( - [ - dmc.Title("Month Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.SEC2_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - ), - flex=1, - ), - dcc.Checklist( - id=ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], - ), - ], - ), - dmc.Group( - [ - dmc.Title("Hour Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.SEC2_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - ), - flex=1, - ), - dcc.Checklist( - id=ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, - options=[ - {"label": "Invert", "value": "invert"} - ], - value=[], - ), - ], - ), - ], - ), dmc.Stack( [ dmc.Button( @@ -330,7 +211,7 @@ def section_two_inputs(): def section_two(): """Return the two graphs in section two.""" return dmc.Stack( - id=ElementIds.TAB6_SEC2_CONTAINER, + id=ElementIds.EXPLORER_SEC2_CONTAINER, children=[ section_two_inputs(), dcc.Loading( @@ -366,7 +247,7 @@ def section_two(): def section_three_inputs(): return dmc.SimpleGrid( - cols=3, + cols=2, children=[ dmc.Stack( [ @@ -375,7 +256,7 @@ def section_three_inputs(): dmc.Title("X Variable:", order=5), dmc.Stack( dropdown( - id=ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, + id=ElementIds.EXPLORER_SEC3_VAR_X_DROPDOWN, options=explore_dropdown_names, value="DBT", ), @@ -388,7 +269,7 @@ def section_three_inputs(): dmc.Title("Y Variable:", order=5), dmc.Stack( dropdown( - id=ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, + id=ElementIds.EXPLORER_SEC3_VAR_Y_DROPDOWN, options=explore_dropdown_names, value=Variables.RH.col_name, ), @@ -401,7 +282,7 @@ def section_three_inputs(): dmc.Title("Color By:", order=5), dmc.Stack( dropdown( - id=ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, + id=ElementIds.EXPLORER_SEC3_COLORBY_DROPDOWN, options=explore_dropdown_names, value="glob_hor_rad", ), @@ -411,64 +292,11 @@ def section_three_inputs(): ), ], ), - dmc.Stack( - [ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, - color="blue", - ), - dmc.Group( - [ - dmc.Title("Month Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, - min=1, - max=12, - value=[1, 12], - marks={1: "1", 12: "12"}, - ), - flex=1, - ), - dcc.Checklist( - id=ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, - options=[{"label": "Invert", "value": "invert"}], - value=[], - ), - ], - ), - dmc.Group( - [ - dmc.Title("Hour Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, - min=0, - max=24, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={ - "always_visible": False, - "placement": "topLeft", - }, - ), - flex=1, - ), - dcc.Checklist( - id=ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, - options=[{"label": "Invert", "value": "invert"}], - value=[], - ), - ], - ), - ], - ), dmc.Stack( [ dmc.Button( "Apply filter", - id=ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, + id=ElementIds.EXPLORER_SEC3_DATA_FILTER_INPUT, color="blue", ), dmc.Group( @@ -476,7 +304,7 @@ def section_three_inputs(): dmc.Title("Filter Variable:", order=5), dmc.Stack( dropdown( - id=ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, + id=ElementIds.EXPLORER_SEC3_FILTER_VAR_DROPDOWN, options=explore_dropdown_names, value=Variables.RH.col_name, ), @@ -489,7 +317,7 @@ def section_three_inputs(): dmc.Title("Min Value:", order=5), dmc.Stack( dmc.NumberInput( - id=ElementIds.TAB6_SEC3_MIN_VAL, + id=ElementIds.EXPLORER_SEC3_MIN_VAL, placeholder="Enter a number for the min val", value=0, ), @@ -502,7 +330,7 @@ def section_three_inputs(): dmc.Title("Max Value:", order=5), dmc.Stack( dmc.NumberInput( - id=ElementIds.TAB6_SEC3_MAX_VAL, + id=ElementIds.EXPLORER_SEC3_MAX_VAL, placeholder="Enter a number for the max val", value=100, ), @@ -549,6 +377,7 @@ def section_three(): Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), @@ -556,9 +385,16 @@ def section_three(): State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) -def update_tab_yearly(_, var, global_local, df, meta, si_ip): +def update_tab_yearly(_, var, global_local, global_filter_data, df, meta, si_ip): """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + target_columns = [var, Variables.ADAPTIVE_CMF_80_LOW.col_name, Variables.ADAPTIVE_CMF_80_UP.col_name, + Variables.ADAPTIVE_CMF_90_LOW.col_name, Variables.ADAPTIVE_CMF_90_UP.col_name, + Variables.ADAPTIVE_CMF_RMT.col_name] + df = apply_global_month_hour_filter(df, global_filter_data, target_columns) + if df[var].mean() == 99990.0: return dmc.Alert( """The selected variable is not available, @@ -583,6 +419,7 @@ def update_tab_yearly(_, var, global_local, df, meta, si_ip): Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), @@ -590,8 +427,12 @@ def update_tab_yearly(_, var, global_local, df, meta, si_ip): State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) -def update_tab_daily(_, var, global_local, df, meta, si_ip): +def update_tab_daily(_, var, global_local, global_filter_data, df, meta, si_ip): """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data) + custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return ( @@ -610,6 +451,7 @@ def update_tab_daily(_, var, global_local, df, meta, si_ip): Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), @@ -617,8 +459,13 @@ def update_tab_daily(_, var, global_local, df, meta, si_ip): State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) -def update_tab_heatmap(_, var, global_local, df, meta, si_ip): +def update_tab_heatmap(_, var, global_local, global_filter_data, df, meta, si_ip): + """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data) + custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return ( @@ -641,54 +488,56 @@ def update_tab_heatmap(_, var, global_local, df, meta, si_ip): [ Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), Input(ElementIds.SEC2_VAR_DROPDOWN, "value"), - Input(ElementIds.SEC2_TIME_FILTER_INPUT, "n_clicks"), Input(ElementIds.SEC2_DATA_FILTER_INPUT, "n_clicks"), Input(ElementIds.NORMALIZE, "value"), Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], # General [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), - State(ElementIds.SEC2_MONTH_SLIDER, "value"), - State(ElementIds.SEC2_HOUR_SLIDER, "value"), State(ElementIds.SEC2_DATA_FILTER_VAR, "value"), State(ElementIds.SEC2_MIN_VAL, "value"), State(ElementIds.SEC2_MAX_VAL, "value"), State(ElementIds.ID_EXPLORER_META_STORE, "data"), - State(ElementIds.INVERT_MONTH_EXPLORE_HEATMAP, "value"), - State(ElementIds.INVERT_HOUR_EXPLORE_HEATMAP, "value"), State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) def update_heatmap( _, var, - time_filter, data_filter, normalize, global_local, + global_filter_data, df, - month, - hour, filter_var, min_val, max_val, meta, - invert_month, - invert_hour, si_ip, ): - df = filter_df_by_month_and_hour( - df, time_filter, month, hour, invert_month, invert_hour, var - ) - data_filter_info = [data_filter, filter_var, min_val, max_val] + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + df = apply_global_month_hour_filter(df, global_filter_data, var) + + filter_state = get_global_filter_state(global_filter_data) + month_range = filter_state["month_range"] + hour_range = filter_state["hour_range"] + invert_month_global = filter_state["invert_month"] + invert_hour_global = filter_state["invert_hour"] + + start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( + month_range, hour_range, invert_month_global, invert_hour_global + ) + else: + # Use default values when global filter is not active + start_month, end_month, start_hour, end_hour = 1, 12, 0, 24 - start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( - month, hour, invert_month, invert_hour - ) + data_filter_info = [data_filter, filter_var, min_val, max_val] month = [start_month, end_month] hour = [start_hour, end_hour] - time_filter_info = [time_filter, month, hour] + time_filter_info = [True, month, hour] heat_map = custom_heatmap( df, global_local, var, time_filter_info, data_filter_info, si_ip @@ -754,23 +603,19 @@ def update_heatmap( [Output(ElementIds.THREE_VAR, "children"), Output(ElementIds.TWO_VAR, "children")], [ Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), - Input(ElementIds.TAB6_SEC3_VAR_X_DROPDOWN, "value"), - Input(ElementIds.TAB6_SEC3_VAR_Y_DROPDOWN, "value"), - Input(ElementIds.TAB6_SEC3_COLORBY_DROPDOWN, "value"), - Input(ElementIds.TAB6_SEC3_TIME_FILTER_INPUT, "n_clicks"), - Input(ElementIds.TAB6_SEC3_DATA_FILTER_INPUT, "n_clicks"), + Input(ElementIds.EXPLORER_SEC3_VAR_X_DROPDOWN, "value"), + Input(ElementIds.EXPLORER_SEC3_VAR_Y_DROPDOWN, "value"), + Input(ElementIds.EXPLORER_SEC3_COLORBY_DROPDOWN, "value"), + Input(ElementIds.EXPLORER_SEC3_DATA_FILTER_INPUT, "n_clicks"), Input(ElementIds.ID_EXPLORER_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), - State(ElementIds.TAB6_SEC3_QUERY_MONTH_SLIDER, "value"), - State(ElementIds.TAB6_SEC3_QUERY_HOUR_SLIDER, "value"), - State(ElementIds.TAB6_SEC3_FILTER_VAR_DROPDOWN, "value"), - State(ElementIds.TAB6_SEC3_MIN_VAL, "value"), - State(ElementIds.TAB6_SEC3_MAX_VAL, "value"), + State(ElementIds.EXPLORER_SEC3_FILTER_VAR_DROPDOWN, "value"), + State(ElementIds.EXPLORER_SEC3_MIN_VAL, "value"), + State(ElementIds.EXPLORER_SEC3_MAX_VAL, "value"), State(ElementIds.ID_EXPLORER_META_STORE, "data"), - State(ElementIds.INVERT_MONTH_EXPLORE_MORE_CHARTS, "value"), - State(ElementIds.INVERT_HOUR_EXPLORE_MORE_CHARTS, "value"), State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), ], ) @@ -779,18 +624,14 @@ def update_more_charts( var_x, var_y, color_by, - time_filter, data_filter, global_local, + global_filter_data, df, - month, - hour, data_filter_var, min_val, max_val, meta, - invert_month, - invert_hour, si_ip, ): """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" @@ -798,9 +639,14 @@ def update_more_charts( # if (min_val3 is None or max_val3 is None) and data_filter3: # raise PreventUpdate - df = filter_df_by_month_and_hour( - df, time_filter, month, hour, invert_month, invert_hour, df.columns - ) + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data) + else: + # Use local filtering when global filter is not active + df = filter_df_by_month_and_hour( + df, True, [1, 12], [0, 24], [], [], df.columns + ) data_filter_info = [data_filter, data_filter_var, min_val, max_val] if data_filter and (min_val is None or max_val is None): @@ -853,38 +699,30 @@ def update_more_charts( [ Input(ElementIds.ID_EXPLORER_DF_STORE, "modified_timestamp"), Input(ElementIds.SEC1_VAR_DROPDOWN, "value"), - Input(ElementIds.SEC1_TIME_FILTER_INPUT, "n_clicks"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_EXPLORER_DF_STORE, "data"), State(ElementIds.ID_EXPLORER_SI_IP_UNIT_STORE, "data"), - State(ElementIds.SEC1_MONTH_SLIDER, "value"), - State(ElementIds.SEC1_HOUR_SLIDER, "value"), - State(ElementIds.INVERT_MONTH_EXPLORE_DESCRIPTIVE, "value"), - State(ElementIds.INVERT_HOUR_EXPLORE_DESCRIPTIVE, "value"), ], ) def update_table( _, dd_value, - __, + global_filter_data, df, si_ip, - month_range, - hour_range, - invert_month, - invert_hour, ): - start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( - month_range, hour_range, invert_month, invert_hour - ) + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + filtered_df = apply_global_month_hour_filter(df, global_filter_data) + # Filter out the filtered rows to avoid empty columns + if '_is_filtered' in filtered_df.columns: + filtered_df = filtered_df[~filtered_df['_is_filtered']] + else: + # Use default values when global filter is not active + filtered_df = df - filtered_df = df[ - (df[Variables.MONTH.col_name] >= start_month) - & (df[Variables.MONTH.col_name] <= end_month) - & (df[Variables.HOUR.col_name] >= start_hour) - & (df[Variables.HOUR.col_name] <= end_hour) - ] return summary_table_tmp_rh_tab( filtered_df[ [ diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index f8541d55..cfee251c 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -60,8 +60,69 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si f" when the {filter_name} is between {min_val} and {max_val} {filter_unit}" ) - fig = go.Figure( - data=go.Heatmap( + fig = go.Figure() + + has_filter_marker = '_is_filtered' in df.columns + + if has_filter_marker and df['_is_filtered'].any(): + filtered_mask = df['_is_filtered'] + if filtered_mask.any(): + original_col = f'_{var}_original' + if original_col in df.columns: + filtered_z = df[original_col].copy() + else: + filtered_z = df[var].copy() + + filtered_z[~filtered_mask] = None + + fig.add_trace(go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.DOY.col_name], + z=filtered_z, + colorscale=[[0, 'lightgray'], [1, 'gray']], + zmin=range_z[0], + zmax=range_z[1], + showscale=False, + connectgaps=False, + hoverongaps=False, + customdata=np.stack((df[Variables.MONTH.col_name], df[Variables.DAY.col_name]), axis=-1), + hovertemplate=( + "Filtered Data
" + + "Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" + " %{y}:00
" + ), + name="filtered", + )) + + normal_mask = ~filtered_mask + normal_z = df[var].copy() + normal_z[filtered_mask] = None + + fig.add_trace(go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.DOY.col_name], + z=normal_z, + colorscale=var_color, + zmin=range_z[0], + zmax=range_z[1], + connectgaps=False, + hoverongaps=False, + customdata=np.stack((df[Variables.MONTH.col_name], df[Variables.DAY.col_name]), axis=-1), + hovertemplate=( + "" + + var + + ": %{z:.2f} " + + var_unit + + "
" + + "Month: %{customdata[0]}
" + + "Day: %{customdata[1]}
" + + "Hour: %{y}:00
" + ), + name="", + colorbar=dict(title=var_unit), + )) + else: + fig.add_trace(go.Heatmap( y=df[Variables.HOUR.col_name], x=df[Variables.DOY.col_name], z=df[var], diff --git a/pages/lib/charts_sun.py b/pages/lib/charts_sun.py index b476636c..dd7eea05 100644 --- a/pages/lib/charts_sun.py +++ b/pages/lib/charts_sun.py @@ -33,6 +33,8 @@ def monthly_solar(epw_df, si_ip): .median() .reset_index() ) + + # Always show 12 months in horizontal layout fig = make_subplots( rows=1, cols=12, @@ -40,18 +42,19 @@ def monthly_solar(epw_df, si_ip): shared_yaxes=True, ) - for i in range(12): + for month_num in range(1, 13): + col_idx = month_num # We only need legend entries for the first pair, since the others repeat. - is_first = i == 0 + is_first = col_idx == 1 fig.add_trace( go.Scatter( x=g_h_rad_month_ave.loc[ - g_h_rad_month_ave[Variables.MONTH.col_name] == i + 1, + g_h_rad_month_ave[Variables.MONTH.col_name] == month_num, Variables.HOUR.col_name, ], y=g_h_rad_month_ave.loc[ - g_h_rad_month_ave[Variables.MONTH.col_name] == i + 1, + g_h_rad_month_ave[Variables.MONTH.col_name] == month_num, Variables.GLOB_HOR_RAD.col_name, ], fill="tozeroy", @@ -61,7 +64,7 @@ def monthly_solar(epw_df, si_ip): name="Global", showlegend=is_first, customdata=epw_df.loc[ - epw_df[Variables.MONTH.col_name] == i + 1, + epw_df[Variables.MONTH.col_name] == month_num, Variables.MONTH_NAMES.col_name, ], hovertemplate=( @@ -78,17 +81,17 @@ def monthly_solar(epw_df, si_ip): ), ), row=1, - col=i + 1, + col=col_idx, ) fig.add_trace( go.Scatter( x=dif_h_rad_month_ave.loc[ - dif_h_rad_month_ave[Variables.MONTH.col_name] == i + 1, + dif_h_rad_month_ave[Variables.MONTH.col_name] == month_num, Variables.HOUR.col_name, ], y=dif_h_rad_month_ave.loc[ - dif_h_rad_month_ave[Variables.MONTH.col_name] == i + 1, + dif_h_rad_month_ave[Variables.MONTH.col_name] == month_num, Variables.DIF_HOR_RAD.col_name, ], fill="tozeroy", @@ -98,7 +101,7 @@ def monthly_solar(epw_df, si_ip): name="Diffuse", showlegend=is_first, customdata=epw_df.loc[ - epw_df[Variables.MONTH.col_name] == i + 1, + epw_df[Variables.MONTH.col_name] == month_num, Variables.MONTH_NAMES.col_name, ], hovertemplate=( @@ -115,10 +118,10 @@ def monthly_solar(epw_df, si_ip): ), ), row=1, - col=i + 1, + col=col_idx, ) - fig.update_xaxes(range=[0, 25], row=1, col=i + 1) + fig.update_xaxes(range=[0, 25], row=1, col=col_idx) if si_ip == UnitSystem.SI: fig.update_yaxes(range=[0, 1000]) diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py index 63709a8e..c88b3df8 100644 --- a/pages/lib/global_element_ids.py +++ b/pages/lib/global_element_ids.py @@ -74,22 +74,22 @@ class ElementIds(str, Enum): SEC2_MONTH_SLIDER = "sec2-month-slider" SEC2_MIN_VAL = "sec2-min-val" SEC2_MAX_VAL = "sec2-max-val" - TAB7_DROPDOWN = "tab7-dropdown" + OUTDOOR_DROPDOWN = "outdoor-dropdown" ID_T_RH_SI_IP_UNIT_STORE = "si-ip-unit-store" SWITCHES_INPUT = "switches-input" TABLE_TMP_HUM = "table-tmp-hum" TABLE_DATA_EXPLORER = "table-data-explorer" - TAB6_SEC2_CONTAINER = "tab6-sec2-container" - TAB6_SEC3_DATA_FILTER_INPUT = "tab6-sec3-data-filter-input" - TAB6_SEC3_FILTER_VAR_DROPDOWN = "tab6-sec3-filter-var-dropdown" - TAB6_SEC3_MIN_VAL = "tab6-sec3-min-val" - TAB6_SEC3_MAX_VAL = "tab6-sec3-max-val" - TAB6_SEC3_TIME_FILTER_INPUT = "tab6-sec3-time-filter-input" - TAB6_SEC3_QUERY_HOUR_SLIDER = "tab6-sec3-query-hour-slider" - TAB6_SEC3_QUERY_MONTH_SLIDER = "tab6-sec3-query-month-slider" - TAB6_SEC3_VAR_X_DROPDOWN = "tab6-sec3-var-x-dropdown" - TAB6_SEC3_VAR_Y_DROPDOWN = "tab6-sec3-var-y-dropdown" - TAB6_SEC3_COLORBY_DROPDOWN = "tab6-sec3-colorby-dropdown" + EXPLORER_SEC2_CONTAINER = "explorer-sec2-container" + EXPLORER_SEC3_DATA_FILTER_INPUT = "explorer-sec3-data-filter-input" + EXPLORER_SEC3_FILTER_VAR_DROPDOWN = "explorer-sec3-filter-var-dropdown" + EXPLORER_SEC3_MIN_VAL = "explorer-sec3-min-val" + EXPLORER_SEC3_MAX_VAL = "explorer-sec3-max-val" + EXPLORER_SEC3_TIME_FILTER_INPUT = "explorer-sec3-time-filter-input" + EXPLORER_SEC3_QUERY_HOUR_SLIDER = "explorer-sec3-query-hour-slider" + EXPLORER_SEC3_QUERY_MONTH_SLIDER = "explorer-sec3-query-month-slider" + EXPLORER_SEC3_VAR_X_DROPDOWN = "explorer-sec3-var-x-dropdown" + EXPLORER_SEC3_VAR_Y_DROPDOWN = "explorer-sec3-var-y-dropdown" + EXPLORER_SEC3_COLORBY_DROPDOWN = "explorer-sec3-colorby-dropdown" MONTH_HOUR_FILTER_OUTDOOR_COMFORT = "month-hour-filter-outdoor-comfort" TWO_VAR = "two-var" THREE_VAR = "three-var" @@ -111,9 +111,9 @@ class ElementIds(str, Enum): CUSTOM_SUN_VIEW_DROPDOWN = "custom-sun-view-dropdown" CUSTOM_SUN_VAR_DROPDOWN = "custom-sun-var-dropdown" CUSTOM_SUNPATH = "custom-sunpath" - TAB_EXPLORE_DROPDOWN = "tab4-explore-dropdown" - TAB4_DAILY = "tab4-daily" - TAB4_HEATMAP = "tab4-heatmap" + SUN_EXPLORE_DROPDOWN = "sun-explore-dropdown" + SUN_DAILY = "sun-daily" + SUN_HEATMAP = "sun-heatmap" STATIC_SECTION = "static-section" TAB_FOUR_CONTAINER = "tab-four-container" MONTHLY_SOLAR = "monthly-solar" @@ -134,7 +134,7 @@ class ElementIds(str, Enum): SUMMER_WIND_ROSE_TEXT = "summer-wind-rose-text" FALL_WIND_ROSE = "fall-wind-rose" FALL_WIND_ROSE_TEXT = "fall-wind-rose-text" - TAB5_DAILY_CONTAINER = "tab5-daily-container" + WIND_DAILY_CONTAINER = "wind-daily-container" DAILY_WIND_ROSE_OUTER_CONTAINER = "daily-wind-rose-outer-container" MORNING_WIND_ROSE = "morning-wind-rose" MORNING_WIND_ROSE_TEXT = "morning-wind-rose-text" @@ -142,11 +142,6 @@ class ElementIds(str, Enum): NOON_WIND_ROSE_TEXT = "noon-wind-rose-text" NIGHT_WIND_ROSE = "night-wind-rose" NIGHT_WIND_ROSE_TEXT = "night-wind-rose-text" - TAB5_CUSTOM_DROPDOWN_CONTAINER = "tab5-custom-dropdown-container" - TAB5_CUSTOM_START_MONTH = "tab5-custom-start-month" - TAB5_CUSTOM_START_HOUR = "tab5-custom-start-hour" - TAB5_CUSTOM_END_MONTH = "tab5-custom-end-month" - TAB5_CUSTOM_END_HOUR = "tab5-custom-end-hour" CUSTOM_WIND_ROSE = "custom-wind-rose" WIND_ROSE = "wind-rose" WIND_SPEED = "wind-speed" @@ -174,7 +169,7 @@ class ElementIds(str, Enum): ID_SELECT_LINES_STORE = "lines-store" TAB_TWO_CONTAINER = "tab-two-container" ID_SUMMARY_SI_IP_RADIO_INPUT = "si-ip-radio-input" - TAB2_SCE1_CONTAINER = "tab2-sec1-container" + SUMMARY_SCE1_CONTAINER = "summary-sec1-container" LOCATION_INFO = "location-info" WORLD_MAP = "world-map" DOWN_EPW_BUTTON = "download-epw-button" @@ -233,3 +228,17 @@ class ElementIds(str, Enum): APP_SHELL = "appshell" NAVBAR_CONTAINER = "navbar-container" TOOLS_MENU_EXPANDED = "tools-menu-expanded" + + # Tools Menu - Unified Filter Controls + TOOLS_APPLY_FILTER = "tools-apply-filter" + TOOLS_APPLY_MONTH_HOUR_FILTER = "tools-apply-month-hour-filter" + TOOLS_FILTER_VAR_DROPDOWN = "tools-filter-var-dropdown" + TOOLS_FILTER_MIN_VAL = "tools-filter-min-val" + TOOLS_FILTER_MAX_VAL = "tools-filter-max-val" + TOOLS_MONTH_SLIDER = "tools-month-slider" + TOOLS_HOUR_SLIDER = "tools-hour-slider" + TOOLS_INVERT_MONTH = "tools-invert-month" + TOOLS_INVERT_HOUR = "tools-invert-hour" + TOOLS_FILTER_SECTION = "tools-filter-section" + TOOLS_MONTH_HOUR_SECTION = "tools-month-hour-section" + TOOLS_GLOBAL_FILTER_STORE = "tools-global-filter-store" \ No newline at end of file diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 90c0f49e..7cfc014f 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -5,6 +5,7 @@ from pages.lib.global_variables import Variables from config import DocLinks, UnitSystem from pages.lib.global_element_ids import ElementIds +from pages.lib.utils import determine_month_and_hour_filter class NavBarIcons: @@ -37,6 +38,110 @@ def get_icon(cls, page_name): """Get icon for a page name.""" return cls._ICON_MAP.get(page_name, "tabler:circle") +# global filters +def create_tools_filter_components(): + # Apply month and hour filter + apply_month_hour_section = dmc.Stack( + id=ElementIds.TOOLS_MONTH_HOUR_SECTION, + children=[ + dmc.Divider( + label="Filter function", size="xs", color="blue", mb="xs" + ), + dmc.Button( + "Apply month and hour filter", + id=ElementIds.TOOLS_APPLY_MONTH_HOUR_FILTER, + color="blue", + variant="light", + size="xs", + mb="xs", + ), + dmc.Stack( + [ + dmc.Text("Month Range:", size="xs", c="dimmed"), + dmc.Stack( + [ + dcc.RangeSlider( + id=ElementIds.TOOLS_MONTH_SLIDER, + min=1, + max=12, + step=1, + value=[1, 12], + marks={1: "1", 12: "12"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, + ), + dmc.Group( + [ + dmc.Switch( + id=ElementIds.TOOLS_INVERT_MONTH, + label="Invert", + checked=False, + size="xs", + color="blue", + style={"fontSize": "0.7rem"}, + ), + ], + justify="flex-end", + ), + ], + gap="xs", + ), + ], + gap="xs", + mb="xs", + ), + dmc.Stack( + [ + dmc.Text("Hour Range:", size="xs", c="dimmed"), + dmc.Stack( + [ + dcc.RangeSlider( + id=ElementIds.TOOLS_HOUR_SLIDER, + min=0, + max=24, + step=1, + value=[0, 24], + marks={0: "0", 24: "24"}, + tooltip={ + "always_visible": False, + "placement": "top", + }, + allowCross=False, + ), + dmc.Group( + [ + dmc.Switch( + id=ElementIds.TOOLS_INVERT_HOUR, + label="Invert", + checked=False, + size="xs", + color="blue", + style={"fontSize": "0.7rem"}, + ), + ], + justify="flex-end", + ), + ], + gap="xs", + ), + ], + gap="xs", + ), + ], + gap="xs", + p="xs", + style={"backgroundColor": "#f8f9fa", "borderRadius": "6px", "border": "1px solid #e9ecef"}, + ) + + return dmc.Stack( + children=[ + apply_month_hour_section, + ], + gap="sm", + ) def create_navbar(): nav_link_styles = { @@ -69,7 +174,7 @@ def create_navbar(): styles=nav_link_styles, ) for page in dash.page_registry.values() - if page[Variables.NAME.col_name] not in ["404"] + if page[Variables.NAME.col_name] not in ["404", "Changelog"] ] parent_group = dmc.NavLink( @@ -138,10 +243,12 @@ def create_navbar(): ], ) + filter_components = create_tools_filter_components() + # Tools controls_group = dmc.NavLink( label="Tools Menu", - children=[controls_stack], + children=[controls_stack, filter_components], id=ElementIds.NAV_GROUP_CONTROLS, variant="light", childrenOffset=0, @@ -317,6 +424,17 @@ def create_stores(): dcc.Store( id=ElementIds.TOOLS_MENU_EXPANDED, data=False, storage_type="session" ), + dcc.Store( + id=ElementIds.TOOLS_GLOBAL_FILTER_STORE, + data={ + "month_range": [1, 12], + "hour_range": [0, 24], + "invert_month": [], + "invert_hour": [], + "filter_active": False + }, + storage_type="session" + ), dcc.Interval( id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, interval=12 * 1000, @@ -406,7 +524,7 @@ def toggle_navbar_and_width( [ Output(f"nav-{page[Variables.PATH.col_name].replace('/', '')}", "active") for page in dash.page_registry.values() - if page[Variables.NAME.col_name] not in ["404"] + if page[Variables.NAME.col_name] not in ["404", "Changelog"] ], Input(ElementIds.MAIN_URL, "pathname"), prevent_initial_call=True, @@ -415,7 +533,7 @@ def update_nav_active_state(pathname): return [ pathname == page[Variables.PATH.col_name] for page in dash.page_registry.values() - if page[Variables.NAME.col_name] not in ["404"] + if page[Variables.NAME.col_name] not in ["404", "Changelog"] ] @@ -426,3 +544,129 @@ def update_nav_active_state(pathname): ) def show_alert_after_delay(n_intervals): return {"display": "block" if n_intervals == 1 else "none"} + + +@callback( + Output(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), + [ + Input(ElementIds.TOOLS_APPLY_MONTH_HOUR_FILTER, "n_clicks"), + ], + [ + State(ElementIds.TOOLS_MONTH_SLIDER, "value"), + State(ElementIds.TOOLS_HOUR_SLIDER, "value"), + State(ElementIds.TOOLS_INVERT_MONTH, "checked"), + State(ElementIds.TOOLS_INVERT_HOUR, "checked"), + State(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), + ], + prevent_initial_call=True, +) +def update_global_filter_state( + apply_clicks, month_range, hour_range, invert_month, invert_hour, current_data +): + if apply_clicks is None: + apply_clicks = 0 + + if apply_clicks > 0: + current_data["filter_active"] = True + + current_data.update({ + "month_range": month_range or [1, 12], + "hour_range": hour_range or [0, 24], + "invert_month": ["invert"] if invert_month else [], + "invert_hour": ["invert"] if invert_hour else [], + }) + + return current_data + + +def get_global_filter_state(filter_store_data): + if not filter_store_data: + return { + "filter_active": False, + "month_range": [1, 12], + "hour_range": [0, 24], + "invert_month": False, + "invert_hour": False, + } + + return { + "filter_active": filter_store_data.get("filter_active", False), + "month_range": filter_store_data.get("month_range", [1, 12]), + "hour_range": filter_store_data.get("hour_range", [0, 24]), + "invert_month": bool(filter_store_data.get("invert_month", [])), + "invert_hour": bool(filter_store_data.get("invert_hour", [])), + } + + +def apply_global_month_hour_filter(df, filter_store_data, target_columns=None): + filter_state = get_global_filter_state(filter_store_data) + + if not filter_state["filter_active"]: + df_copy = df.copy() + df_copy['_is_filtered'] = False + return df_copy + + month_range = filter_state["month_range"] + hour_range = filter_state["hour_range"] + invert_month = filter_state["invert_month"] + invert_hour = filter_state["invert_hour"] + + start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( + month_range, hour_range, invert_month, invert_hour + ) + + df_copy = df.copy() + + if target_columns is None: + target_columns = [Variables.DBT.col_name] + elif isinstance(target_columns, str): + target_columns = [target_columns] + + month_mask = None + if start_month <= end_month: + month_mask = (df_copy[Variables.MONTH.col_name] < start_month) | (df_copy[Variables.MONTH.col_name] > end_month) + else: + month_mask = (df_copy[Variables.MONTH.col_name] >= end_month) & (df_copy[Variables.MONTH.col_name] <= start_month) + + hour_mask = None + if start_hour <= end_hour: + hour_mask = (df_copy[Variables.HOUR.col_name] < start_hour) | (df_copy[Variables.HOUR.col_name] > end_hour) + else: + hour_mask = (df_copy[Variables.HOUR.col_name] >= end_hour) & (df_copy[Variables.HOUR.col_name] <= start_hour) + + df_copy['_is_filtered'] = month_mask | hour_mask + + for target_col in target_columns: + df_copy[f'_{target_col}_original'] = df_copy[target_col] + + from pages.lib.template_graphs import time_filtering + time_filtering(df_copy, start_month, end_month, Variables.MONTH.col_name, target_col) + time_filtering(df_copy, start_hour, end_hour, Variables.MONTH.col_name, target_col) + + return df_copy + + +@callback( + [ + Output(ElementIds.TOOLS_MONTH_SLIDER, "value"), + Output(ElementIds.TOOLS_HOUR_SLIDER, "value"), + Output(ElementIds.TOOLS_INVERT_MONTH, "checked"), + Output(ElementIds.TOOLS_INVERT_HOUR, "checked"), + ], + [ + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), + ], + prevent_initial_call=False, +) +def sync_sliders_with_global_state(global_filter_data): + if not global_filter_data: + return [1, 12], [0, 24], False, False + + return ( + global_filter_data.get("month_range", [1, 12]), + global_filter_data.get("hour_range", [0, 24]), + bool(global_filter_data.get("invert_month", [])), + bool(global_filter_data.get("invert_hour", [])), + ) + + diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 1fb189df..633fc3ae 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -378,10 +378,18 @@ def heatmap_with_filter( var_range = variable.get_range(si_ip) var_color = variable.get_color() + has_global_filter_marker = '_is_filtered' in df.columns + global_filter_mask = None + if has_global_filter_marker: + global_filter_mask = df['_is_filtered'].copy() + df = filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, var ) + if has_global_filter_marker and global_filter_mask is not None: + df['_is_filtered'] = global_filter_mask + start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( month, hour, invert_month, invert_hour ) @@ -404,8 +412,63 @@ def heatmap_with_filter( # Set maximum and minimum according to data data_max, data_min = get_max_min_value(df[var]) range_z = [data_min, data_max] - fig = go.Figure( - data=go.Heatmap( + fig = go.Figure() + + has_filter_marker = '_is_filtered' in df.columns + + if has_filter_marker and df['_is_filtered'].any(): + filtered_mask = df['_is_filtered'] + if filtered_mask.any(): + original_col = f'_{var}_original' + if original_col in df.columns: + filtered_z = df[original_col].copy() + else: + filtered_z = df[var].copy() + + filtered_z[~filtered_mask] = None + + fig.add_trace(go.Heatmap( + y=df[Variables.HOUR.col_name] - 0.5, + x=df[Variables.UTC_TIME.col_name].dt.date, + z=filtered_z, + colorscale=[[0, 'lightgray'], [1, 'gray']], + zmin=range_z[0], + zmax=range_z[1], + showscale=False, + customdata=np.stack((df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), axis=-1), + hovertemplate=( + "Filtered Data
" + + "Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" + " %{y}:00
" + ), + name="filtered", + )) + + normal_mask = ~filtered_mask + normal_z = df[var].copy() + normal_z[filtered_mask] = None + + fig.add_trace(go.Heatmap( + y=df[Variables.HOUR.col_name] - 0.5, + x=df[Variables.UTC_TIME.col_name].dt.date, + z=normal_z, + colorscale=var_color, + zmin=range_z[0], + zmax=range_z[1], + customdata=np.stack((df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), axis=-1), + hovertemplate=( + "" + + var + + ": %{z:.2f} " + + var_unit + + "
Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" + " %{y}:00
" + ), + name="", + colorbar=dict(title="") if "_categories" in var else dict(title=var_unit), + )) + else: + fig.add_trace(go.Heatmap( y=df[Variables.HOUR.col_name] - 0.5, # Offset by 0.5 to center the hour labels x=df[Variables.UTC_TIME.col_name].dt.date, @@ -426,7 +489,7 @@ def heatmap_with_filter( " %{y}:00
" ), name="", - colorbar=dict(title=var_unit), + colorbar=dict(title="") if "_categories" in var else dict(title=var_unit), ) ) @@ -473,8 +536,63 @@ def heatmap(df, var, global_local, si_ip): # Set maximum and minimum according to data data_max, data_min = get_max_min_value(df[var]) range_z = [data_min, data_max] - fig = go.Figure( - data=go.Heatmap( + fig = go.Figure() + + has_filter_marker = '_is_filtered' in df.columns + + if has_filter_marker and df['_is_filtered'].any(): + filtered_mask = df['_is_filtered'] + if filtered_mask.any(): + original_col = f'_{var}_original' + if original_col in df.columns: + filtered_z = df[original_col].copy() + else: + filtered_z = df[var].copy() + + filtered_z[~filtered_mask] = None + + fig.add_trace(go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.UTC_TIME.col_name].dt.date, + z=filtered_z, + colorscale=[[0, 'lightgray'], [1, 'gray']], + zmin=range_z[0], + zmax=range_z[1], + showscale=False, + customdata=np.stack((df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), axis=-1), + hovertemplate=( + "Filtered Data
" + + "Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" + " %{y}:00
" + ), + name="filtered", + )) + + normal_mask = ~filtered_mask + normal_z = df[var].copy() + normal_z[filtered_mask] = None + + fig.add_trace(go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.UTC_TIME.col_name].dt.date, + z=normal_z, + colorscale=var_color, + zmin=range_z[0], + zmax=range_z[1], + customdata=np.stack((df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), axis=-1), + hovertemplate=( + "" + + var + + ": %{z:.2f} " + + var_unit + + "
Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" + " %{y}:00
" + ), + name="", + colorbar=dict(title=var_unit), + )) + else: + fig.add_trace(go.Heatmap( y=df[Variables.HOUR.col_name], x=df[Variables.UTC_TIME.col_name].dt.date, z=df[var], @@ -529,35 +647,36 @@ def speed_labels(bins, units): return labels -def wind_rose(df, title, month, hour, labels, si_ip): +def wind_rose(df, title, month, hour, labels, si_ip, skip_time_filter=False): """Return the wind rose figure. Based on: https://gist.github.com/phobson/41b41bdd157a2bcf6e14 """ - start_month = month[0] - end_month = month[1] - start_hour = hour[0] - end_hour = hour[1] - if start_month <= end_month: - df = df.loc[ - (df[Variables.MONTH.col_name] >= start_month) - & (df[Variables.MONTH.col_name] <= end_month) - ] - else: - df = df.loc[ - (df[Variables.MONTH.col_name] <= end_month) - | (df[Variables.MONTH.col_name] >= start_month) - ] - if start_hour <= end_hour: - df = df.loc[ - (df[Variables.HOUR.col_name] > start_hour) - & (df[Variables.HOUR.col_name] <= end_hour) - ] - else: - df = df.loc[ - (df[Variables.HOUR.col_name] <= end_hour) - | (df[Variables.HOUR.col_name] >= start_hour) - ] + if not skip_time_filter: + start_month = month[0] + end_month = month[1] + start_hour = hour[0] + end_hour = hour[1] + if start_month <= end_month: + df = df.loc[ + (df[Variables.MONTH.col_name] >= start_month) + & (df[Variables.MONTH.col_name] <= end_month) + ] + else: + df = df.loc[ + (df[Variables.MONTH.col_name] <= end_month) + | (df[Variables.MONTH.col_name] >= start_month) + ] + if start_hour <= end_hour: + df = df.loc[ + (df[Variables.HOUR.col_name] > start_hour) + & (df[Variables.HOUR.col_name] <= end_hour) + ] + else: + df = df.loc[ + (df[Variables.HOUR.col_name] <= end_hour) + | (df[Variables.HOUR.col_name] >= start_hour) + ] wind_speed_variable = VariableInfo.from_col_name(Variables.WIND_SPEED.col_name) @@ -784,8 +903,11 @@ def thermal_stress_stacked_barchart( linecolor="black", mirror=True, ) + # Get available months from filtered data + available_months = sorted(new_df[Variables.MONTH.col_name].unique()) + fig.update_xaxes( - dict(tickmode="array", tickvals=np.arange(0, 12, 1), ticktext=month_lst), + dict(tickmode="array", tickvals=np.arange(0, len(available_months), 1), ticktext=month_lst), title_text="Day", showline=True, linewidth=1, @@ -831,31 +953,43 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): if len(time_filter_info) == 1: filter_var = str(var) - for i in range(1, 13): - query = ( - f"month=={str(i)} and ({filter_var}>={min_val} and {filter_var}<={max_val})" - ) - a = new_df.query(query)[Variables.DOY.col_name].count() - month_in.append(a) - query = f"month=={str(i)} and ({filter_var}<{min_val})" - b = new_df.query(query)[Variables.DOY.col_name].count() - month_below.append(b) - query = f"month=={str(i)} and {filter_var}>{max_val}" - c = new_df.query(query)[Variables.DOY.col_name].count() - month_above.append(c) + # Always process all 12 months + available_months_set = set(new_df[Variables.MONTH.col_name].unique()) + + for month_num in range(1, 13): + if month_num in available_months_set: + query = ( + f"month=={str(month_num)} and ({filter_var}>={min_val} and {filter_var}<={max_val})" + ) + a = new_df.query(query)[Variables.DOY.col_name].count() + month_in.append(a) + query = f"month=={str(month_num)} and ({filter_var}<{min_val})" + b = new_df.query(query)[Variables.DOY.col_name].count() + month_below.append(b) + query = f"month=={str(month_num)} and {filter_var}>{max_val}" + c = new_df.query(query)[Variables.DOY.col_name].count() + month_above.append(c) + else: + # No data for this month, append zeros + month_in.append(0) + month_below.append(0) + month_above.append(0) go.Figure() + + month_names = month_lst + trace1 = go.Bar( - x=list(range(0, 13)), y=month_in, name="IN range", marker_color=color_in + x=month_names, y=month_in, name="IN range", marker_color=color_in ) trace2 = go.Bar( - x=list(range(0, 13)), + x=month_names, y=month_below, name="BELOW range", marker_color=color_below, ) trace3 = go.Bar( - x=list(range(0, 13)), + x=month_names, y=month_above, name="ABOVE range", marker_color=color_above, diff --git a/pages/lib/utils.py b/pages/lib/utils.py index ea553953..30d5e432 100644 --- a/pages/lib/utils.py +++ b/pages/lib/utils.py @@ -268,14 +268,10 @@ def summary_table_tmp_rh_tab(df, value, si_ip): def determine_month_and_hour_filter(month, hour, invert_month, invert_hour): start_month, end_month = month - if invert_month == [Variables.INVERT.col_name] and ( - start_month != 1 or end_month != 12 - ): + if invert_month and (start_month != 1 or end_month != 12): end_month, start_month = month start_hour, end_hour = hour - if invert_hour == [Variables.INVERT.col_name] and ( - start_hour != 0 or end_hour != 24 - ): + if invert_hour and (start_hour != 0 or end_hour != 24): end_hour, start_hour = hour return start_month, end_month, start_hour, end_hour diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index a2485bbf..17499644 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -99,7 +99,7 @@ def update_layout(si_ip): def inputs_tab(t_min, t_max, d_set): return dmc.SimpleGrid( - cols=3, + cols=2, spacing="md", children=[ dmc.Stack( @@ -142,58 +142,6 @@ def inputs_tab(t_min, t_max, d_set): ), ] ), - dmc.Stack( - [ - dmc.Button( - "Apply month and hour filter", - color="blue", - id=ElementIds.NV_MONTH_HOUR_FILTER, - variant="link", - ), - dmc.Group( - [ - dmc.Title("Month Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.NV_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - ), - flex=1, - ), - dcc.Checklist( - options=[{"label": "Invert", "value": "invert"}], - value=[], - id=ElementIds.INVERT_MONTH_NV, - ), - ], - ), - dmc.Group( - [ - dmc.Title("Hour Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.NV_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - ), - flex=1, - ), - dcc.Checklist( - options=[{"label": "Invert", "value": "invert"}], - value=[], - id=ElementIds.INVERT_HOUR_NV, - ), - ], - ), - ] - ), dmc.Stack( [ dmc.Button( @@ -241,41 +189,33 @@ def inputs_tab(t_min, t_max, d_set): Output(ElementIds.NV_HEATMAP_CHART, "children"), [ Input(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "modified_timestamp"), - Input(ElementIds.NV_MONTH_HOUR_FILTER, "n_clicks"), Input(ElementIds.NV_DBT_FILTER, "n_clicks"), Input(ElementIds.NV_DPT_FILTER, "n_clicks"), Input(ElementIds.ID_NATURAL_VENTILATION_GLOBAL_LOCAL_RADIO_INPUT, "value"), Input(ElementIds.ENABLE_CONDENSATION, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "data"), - State(ElementIds.NV_MONTH_SLIDER, "value"), - State(ElementIds.NV_HOUR_SLIDER, "value"), State(ElementIds.NV_TDB_MIN_VAL, "value"), State(ElementIds.NV_TDB_MAX_VAL, "value"), State(ElementIds.NV_DPT_MAX_VAL, "value"), State(ElementIds.ID_NATURAL_VENTILATION_META_STORE, "data"), - State(ElementIds.INVERT_MONTH_NV, "value"), - State(ElementIds.INVERT_HOUR_NV, "value"), State(ElementIds.ID_NATURAL_VENTILATION_SI_IP_UNIT_STORE, "data"), ], ) def nv_heatmap( ts, - time_filter, dbt_data_filter, click_dpt_filter, global_local, condensation_enabled, + global_filter_data, df, - month, - hour, min_dbt_val, max_dbt_val, max_dpt_val, meta, - invert_month, - invert_hour, si_ip, ): if df is None: @@ -283,9 +223,22 @@ def nv_heatmap( # enable or disable button apply filter DPT dpt_data_filter = enable_dew_point_data_filter(condensation_enabled) - start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( - month, hour, invert_month, invert_hour - ) + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + df = apply_global_month_hour_filter(df, global_filter_data, Variables.DBT.col_name) + + filter_state = get_global_filter_state(global_filter_data) + month_range = filter_state["month_range"] + hour_range = filter_state["hour_range"] + invert_month_global = filter_state["invert_month"] + invert_hour_global = filter_state["invert_hour"] + + start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( + month_range, hour_range, invert_month_global, invert_hour_global + ) + else: + # Use default values when global filter is not active + start_month, end_month, start_hour, end_hour = 1, 12, 0, 24 var = Variables.DBT.col_name filter_var = Variables.DPT.col_name @@ -311,9 +264,6 @@ def nv_heatmap( ), ) - df = filter_df_by_month_and_hour( - df, time_filter, month, hour, invert_month, invert_hour, var - ) variable = VariableInfo.from_col_name(var) filter = VariableInfo.from_col_name(filter_var) @@ -338,7 +288,8 @@ def nv_heatmap( f" {max_dbt_val} {var_unit}" ) - if time_filter: + # Title will be updated based on global filter state + if global_filter_data and global_filter_data.get("filter_active", False): title += ( f" between the months of {month_lst[start_month - 1]} and " f"{month_lst[end_month - 1]}
and between the hours {start_hour}" @@ -416,52 +367,40 @@ def nv_heatmap( Output(ElementIds.NV_BAR_CHART, "children"), [ Input(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "modified_timestamp"), - Input(ElementIds.NV_MONTH_HOUR_FILTER, "n_clicks"), Input(ElementIds.NV_DBT_FILTER, "n_clicks"), Input(ElementIds.NV_DPT_FILTER, "n_clicks"), Input(ElementIds.ID_NATURAL_VENTILATION_GLOBAL_LOCAL_RADIO_INPUT, "value"), Input(ElementIds.SWITCHES_INPUT, "checked"), Input(ElementIds.ENABLE_CONDENSATION, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_NATURAL_VENTILATION_DF_STORE, "data"), - State(ElementIds.NV_MONTH_SLIDER, "value"), - State(ElementIds.NV_HOUR_SLIDER, "value"), State(ElementIds.NV_TDB_MIN_VAL, "value"), State(ElementIds.NV_TDB_MAX_VAL, "value"), State(ElementIds.NV_DPT_MAX_VAL, "value"), State(ElementIds.ID_NATURAL_VENTILATION_META_STORE, "data"), - State(ElementIds.INVERT_MONTH_NV, "value"), - State(ElementIds.INVERT_HOUR_NV, "value"), State(ElementIds.ID_NATURAL_VENTILATION_SI_IP_UNIT_STORE, "data"), ], ) def nv_bar_chart( ts, - time_filter, dbt_data_filter, click_dpt_filter, global_local, normalize, condensation_enabled, + global_filter_data, df, - month, - hour, min_dbt_val, max_dbt_val, max_dpt_val, meta, - invert_month, - invert_hour, si_ip, ): # enable or disable button apply filter DPT dpt_data_filter = enable_dew_point_data_filter(condensation_enabled) - start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( - month, hour, invert_month, invert_hour - ) - var = Variables.DBT.col_name filter_var = Variables.DPT.col_name @@ -479,9 +418,22 @@ def nv_bar_chart( df[Variables.NV_ALLOWED.col_name] = 1 - df = filter_df_by_month_and_hour( - df, time_filter, month, hour, invert_month, invert_hour, "nv_allowed" - ) + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + df = apply_global_month_hour_filter(df, global_filter_data, Variables.NV_ALLOWED.col_name) + + filter_state = get_global_filter_state(global_filter_data) + month_range = filter_state["month_range"] + hour_range = filter_state["hour_range"] + invert_month_global = filter_state["invert_month"] + invert_hour_global = filter_state["invert_hour"] + + start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( + month_range, hour_range, invert_month_global, invert_hour_global + ) + else: + # Use default values when global filter is not active + start_month, end_month, start_hour, end_hour = 1, 12, 0, 24 # this should be the total after filtering by time tot_month_hours = ( @@ -558,7 +510,7 @@ def nv_bar_chart( ) fig.update_yaxes(title_text="Percentage (%)", range=[0, 100]) - if time_filter: + if global_filter_data and global_filter_data.get("filter_active", False): title += ( f" between the months of {month_lst[start_month - 1]} and " f"{month_lst[end_month - 1]} and between
the hours {start_hour}" diff --git a/pages/outdoor.py b/pages/outdoor.py index d0483c47..538041c4 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -44,7 +44,7 @@ def inputs_outdoor_comfort(): dmc.Title("Select a scenario:", order=5), dmc.Stack( dropdown( - id=ElementIds.TAB7_DROPDOWN, + id=ElementIds.OUTDOOR_DROPDOWN, options=outdoor_dropdown_names, value="utci_Sun_Wind", persistence=True, @@ -56,60 +56,6 @@ def inputs_outdoor_comfort(): ], align="flex-start", ), - dmc.Stack( - [ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, - variant="filled", - color="blue", - ), - dmc.Group( - [ - dmc.Title("Month Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={"always_visible": False}, - ), - flex=1, - ), - dcc.Checklist( - id=ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, - options=[{"label": "Invert", "value": "invert"}], - value=[], - ), - ], - ), - dmc.Group( - [ - dmc.Title("Hour Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={"always_visible": False}, - ), - flex=1, - ), - dcc.Checklist( - id=ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, - options=[{"label": "Invert", "value": "invert"}], - value=[], - ), - ], - ), - ], - ), ], ) @@ -230,40 +176,71 @@ def update_outdoor_comfort_output(_, df): cols_with_the_highest_number_of_zero.append(col) elif count == highest_count: cols_with_the_highest_number_of_zero.append(col) - return f"The Best Weather Condition is: {', '.join(cols_with_the_highest_number_of_zero)}" + + # Convert column names to display names using string replacement + display_names = [] + for col in cols_with_the_highest_number_of_zero: + # Remove utci_ prefix and replace all underscores with spaces + display_name = col.replace("utci_", "UTCI ") + display_name = display_name.replace("_", " ") + display_name = display_name.replace("categories", "Categories") + # Fix specific words that need spaces + display_name = display_name.replace("noSun", "No Sun") + display_name = display_name.replace("noWind", "No Wind") + display_name = display_name.replace("Sun", "Sun") # Keep Sun as is + display_name = display_name.replace("Wind", "Wind") # Keep Wind as is + display_names.append(display_name) + + return f"The Best Weather Condition is: {', '.join(display_names)}" @callback( Output(ElementIds.UTCI_HEATMAP, "children"), [ Input(ElementIds.ID_OUTDOOR_DF_STORE, "modified_timestamp"), - Input(ElementIds.TAB7_DROPDOWN, "value"), + Input(ElementIds.OUTDOOR_DROPDOWN, "value"), Input(ElementIds.ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT, "value"), - Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), State(ElementIds.ID_OUTDOOR_META_STORE, "data"), State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), - State(ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, "value"), - State(ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, "value"), - State(ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, "value"), - State(ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, "value"), ], ) def update_tab_utci_value( _, var, global_local, - time_filter, + global_filter_data, df, meta, si_ip, - month, - hour, - invert_month, - invert_hour, ): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + df = apply_global_month_hour_filter(df, global_filter_data, var) + + filter_state = get_global_filter_state(global_filter_data) + month_range = filter_state["month_range"] + hour_range = filter_state["hour_range"] + invert_month_global = filter_state["invert_month"] + invert_hour_global = filter_state["invert_hour"] + + # time_filter is always True for global filter + time_filter = True + month = month_range + hour = hour_range + invert_month = invert_month_global + invert_hour = invert_hour_global + else: + # Use default values when global filter is not active + time_filter = True + month = [1, 12] + hour = [0, 24] + invert_month = [] + invert_hour = [] + custom_inputs = f"{var}" units = generate_units_degree(si_ip) return dcc.Graph( @@ -285,7 +262,7 @@ def update_tab_utci_value( @callback( Output(ElementIds.IMAGE_SELECTION, "children"), - Input(ElementIds.TAB7_DROPDOWN, "value"), + Input(ElementIds.OUTDOOR_DROPDOWN, "value"), ) def change_image_based_on_selection(value): if value == "utci_Sun_Wind": @@ -304,33 +281,49 @@ def change_image_based_on_selection(value): Output(ElementIds.UTCI_CATEGORY_HEATMAP, "children"), [ Input(ElementIds.ID_OUTDOOR_DF_STORE, "modified_timestamp"), - Input(ElementIds.TAB7_DROPDOWN, "value"), + Input(ElementIds.OUTDOOR_DROPDOWN, "value"), Input(ElementIds.ID_OUTDOOR_GLOBAL_LOCAL_RADIO_INPUT, "value"), - Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), State(ElementIds.ID_OUTDOOR_META_STORE, "data"), State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), - State(ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, "value"), - State(ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, "value"), - State(ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, "value"), - State(ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, "value"), ], ) def update_tab_utci_category( _, var, global_local, - time_filter, + global_filter_data, df, meta, si_ip, - month, - hour, - invert_month, - invert_hour, ): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + df = apply_global_month_hour_filter(df, global_filter_data, [var, var + "_categories"]) + + filter_state = get_global_filter_state(global_filter_data) + month_range = filter_state["month_range"] + hour_range = filter_state["hour_range"] + invert_month_global = filter_state["invert_month"] + invert_hour_global = filter_state["invert_hour"] + + # time_filter is always True for global filter + time_filter = True + month = month_range + hour = hour_range + invert_month = invert_month_global + invert_hour = invert_hour_global + else: + # time_filter is always True for local filter too + time_filter = True + month = [1, 12] + hour = [0, 24] + invert_month = [] + invert_hour = [] + utci_stress_cat = heatmap_with_filter( df, var + "_categories", @@ -343,7 +336,9 @@ def update_tab_utci_category( invert_hour, "UTCI thermal stress", ) - utci_stress_cat["data"][0]["colorbar"] = dict( + colorbar_index = 1 if len(utci_stress_cat["data"]) > 1 else 0 + + utci_stress_cat["data"][colorbar_index]["colorbar"] = dict( title="Thermal stress", titleside="top", tickmode="array", @@ -375,23 +370,43 @@ def update_tab_utci_category( @callback( Output(ElementIds.UTCI_SUMMARY_CHART, "children"), [ - Input(ElementIds.TAB7_DROPDOWN, "value"), - Input(ElementIds.MONTH_HOUR_FILTER_OUTDOOR_COMFORT, "n_clicks"), + Input(ElementIds.OUTDOOR_DROPDOWN, "value"), Input(ElementIds.OUTDOOR_COMFORT_SWITCHES_INPUT, "checked"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_OUTDOOR_DF_STORE, "data"), - State(ElementIds.OUTDOOR_COMFORT_MONTH_SLIDER, "value"), - State(ElementIds.OUTDOOR_COMFORT_HOUR_SLIDER, "value"), State(ElementIds.ID_OUTDOOR_META_STORE, "data"), - State(ElementIds.INVERT_MONTH_OUTDOOR_COMFORT, "value"), - State(ElementIds.INVERT_HOUR_OUTDOOR_COMFORT, "value"), State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), ], ) def update_tab_utci_summary_chart( - var, time_filter, normalize, df, month, hour, meta, invert_month, invert_hour, si_ip + var, normalize, global_filter_data, df, meta, si_ip ): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + df = apply_global_month_hour_filter(df, global_filter_data, var) + + filter_state = get_global_filter_state(global_filter_data) + month_range = filter_state["month_range"] + hour_range = filter_state["hour_range"] + invert_month_global = filter_state["invert_month"] + invert_hour_global = filter_state["invert_hour"] + + # time_filter is always True for global filter + time_filter = True + month = month_range + hour = hour_range + invert_month = invert_month_global + invert_hour = invert_hour_global + else: + # time_filter is always True for local filter too + time_filter = True + month = [1, 12] + hour = [0, 24] + invert_month = [] + invert_hour = [] + utci_summary_chart = thermal_stress_stacked_barchart( df, var + "_categories", diff --git a/pages/psy-chart.py b/pages/psy-chart.py index b17969a9..48a1ae4f 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -57,7 +57,7 @@ def inputs(): return dmc.SimpleGrid( - cols=3, + cols=2, children=[ dmc.Group( [ @@ -75,59 +75,6 @@ def inputs(): ], align="flex-start", ), - dmc.Stack( - [ - dmc.Button( - "Apply month and hour filter", - id=ElementIds.MONTH_HOUR_FILTER, - color="blue", - ), - dmc.Group( - [ - dmc.Title("Month Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.PSY_MONTH_SLIDER, - min=1, - max=12, - step=1, - value=[1, 12], - marks={1: "1", 12: "12"}, - tooltip={"always_visible": False}, - ), - flex=1, - ), - dcc.Checklist( - id=ElementIds.INVERT_MONTH_PSY, - options=[{"label": "Invert", "value": "invert"}], - value=[], - ), - ], - ), - dmc.Group( - [ - dmc.Title("Hour Range", order=5), - dmc.Stack( - dcc.RangeSlider( - id=ElementIds.PSY_HOUR_SLIDER, - min=0, - max=24, - step=1, - value=[0, 24], - marks={0: "0", 24: "24"}, - tooltip={"always_visible": False}, - ), - flex=1, - ), - dcc.Checklist( - id=ElementIds.INVERT_HOUR_PSY, - options=[{"label": "Invert", "value": "invert"}], - value=[], - ), - ], - ), - ], - ), dmc.Stack( [ dmc.Button( @@ -211,47 +158,53 @@ def layout(): [ Input(ElementIds.ID_PSY_CHART_DF_STORE, "modified_timestamp"), Input(ElementIds.PSY_COLOR_BY_DROPDOWN, "value"), - Input(ElementIds.MONTH_HOUR_FILTER, "n_clicks"), Input(ElementIds.DATA_FILTER, "n_clicks"), Input(ElementIds.ID_PSY_CHART_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_PSY_CHART_DF_STORE, "data"), - State(ElementIds.PSY_MONTH_SLIDER, "value"), - State(ElementIds.PSY_HOUR_SLIDER, "value"), State(ElementIds.PSY_MIN_VAL, "value"), State(ElementIds.PSY_MAX_VAL, "value"), State(ElementIds.PSY_VAR_DROPDOWN, "value"), State(ElementIds.ID_PSY_CHART_META_STORE, "data"), - State(ElementIds.INVERT_MONTH_PSY, "value"), - State(ElementIds.INVERT_HOUR_PSY, "value"), State(ElementIds.ID_PSY_CHART_SI_IP_UNIT_STORE, "data"), ], ) def update_psych_chart( ts, colorby_var, - time_filter, data_filter, global_local, + global_filter_data, df, - month, - hour, min_val, max_val, data_filter_var, meta, - invert_month, - invert_hour, si_ip, ): - start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( - month, hour, invert_month, invert_hour - ) + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + df = apply_global_month_hour_filter(df, global_filter_data) + + filter_state = get_global_filter_state(global_filter_data) + month_range = filter_state["month_range"] + hour_range = filter_state["hour_range"] + invert_month_global = filter_state["invert_month"] + invert_hour_global = filter_state["invert_hour"] + + start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( + month_range, hour_range, invert_month_global, invert_hour_global + ) + else: + # Use default values when global filter is not active + start_month, end_month, start_hour, end_hour = 1, 12, 0, 24 - df = filter_df_by_month_and_hour( - df, time_filter, month, hour, invert_month, invert_hour, df.columns - ) + # Use local filtering when global filter is not active + df = filter_df_by_month_and_hour( + df, True, [1, 12], [0, 24], [], [], df.columns + ) if data_filter: if min_val <= max_val: diff --git a/pages/select.py b/pages/select.py index 498cc67b..d2387b91 100644 --- a/pages/select.py +++ b/pages/select.py @@ -125,6 +125,7 @@ def alert(): Output(ElementIds.ALERT, "visible"), Output(ElementIds.ALERT, "children"), Output(ElementIds.ALERT, "color"), + Output(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data", allow_duplicate=True), ], [ Input(ElementIds.MODAL_YES_BUTTON, "n_clicks"), @@ -157,6 +158,13 @@ def submitted_data( True, messages_alert["not_available"], "orange", + { + "month_range": [1, 12], + "hour_range": [0, 24], + "invert_month": [], + "invert_hour": [], + "filter_active": False + }, ) location_info = get_location_info( lines, url_store @@ -167,6 +175,13 @@ def submitted_data( True, messages_alert["success"], "green", + { + "month_range": [1, 12], + "hour_range": [0, 24], + "invert_month": [], + "invert_hour": [], + "filter_active": False + }, ) elif ( @@ -191,6 +206,13 @@ def submitted_data( True, messages_alert["success"], "green", + { + "month_range": [1, 12], + "hour_range": [0, 24], + "invert_month": [], + "invert_hour": [], + "filter_active": False + }, ) else: return ( @@ -199,6 +221,13 @@ def submitted_data( True, messages_alert["invalid_format"], "orange", + { + "month_range": [1, 12], + "hour_range": [0, 24], + "invert_month": [], + "invert_hour": [], + "filter_active": False + }, ) except (ValueError, IndexError, KeyError) as e: print(f"Error parsing EPW file: {e}") @@ -208,6 +237,13 @@ def submitted_data( True, messages_alert["wrong_extension"], "orange", + { + "month_range": [1, 12], + "hour_range": [0, 24], + "invert_month": [], + "invert_hour": [], + "filter_active": False + }, ) raise PreventUpdate @@ -252,7 +288,6 @@ def switch_si_ip(_, si_ip_input, url_store, lines): 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"), ], [ @@ -274,7 +309,6 @@ def enable_tabs_when_data_is_loaded(meta, data): True, True, True, - True, # changelog always disabled default, ) else: @@ -288,7 +322,6 @@ def enable_tabs_when_data_is_loaded(meta, data): False, False, False, - True, # changelog always disabled "Current Location: " + meta[Variables.CITY.col_name] + ", " diff --git a/pages/summary.py b/pages/summary.py index e6512738..bed97518 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -51,7 +51,7 @@ def update_layout(si_ip): cooling_setpoint = 64 return dmc.Stack( - id=ElementIds.TAB2_SCE1_CONTAINER, + id=ElementIds.SUMMARY_SCE1_CONTAINER, children=[ dcc.Loading( type="circle", @@ -273,6 +273,7 @@ def update_location_info(ts, df, meta, si_ip): [ Input(ElementIds.ID_SUMMARY_DF_STORE, "modified_timestamp"), Input(ElementIds.SUBMIT_SET_POINTS, "n_clicks"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_SUMMARY_DF_STORE, "data"), @@ -283,7 +284,7 @@ def update_location_info(ts, df, meta, si_ip): ], prevent_initial_call=False, ) -def degree_day_chart(ts, n_clicks, df, meta, hdd_value, cdd_value, si_ip): +def degree_day_chart(ts, n_clicks, global_filter_data, df, meta, hdd_value, cdd_value, si_ip): """Redraw HDD/CDD chart only when Submit is clicked.""" if df is None or meta is None: @@ -292,6 +293,11 @@ def degree_day_chart(ts, n_clicks, df, meta, hdd_value, cdd_value, si_ip): if isinstance(df, (list, tuple, dict)): df = pd.DataFrame(df) + # Apply global filter if active + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data, Variables.DBT.col_name) + hdd_setpoint = hdd_value cdd_setpoint = cdd_value warning_setpoint = cdd_setpoint < hdd_setpoint diff --git a/pages/sun.py b/pages/sun.py index 3999dac3..539a9101 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -125,16 +125,16 @@ def explore_daily_heatmap(): children=[ dmc.Title("Select variable: ", order=5), dropdown( - id=ElementIds.TAB_EXPLORE_DROPDOWN, + id=ElementIds.SUN_EXPLORE_DROPDOWN, options=sun_cloud_tab_explore_dropdown_names, value="glob_hor_rad", ), ], ), - dcc.Loading(type="circle", children=dmc.Stack(id=ElementIds.TAB4_DAILY)), + dcc.Loading(type="circle", children=dmc.Stack(id=ElementIds.SUN_DAILY)), dcc.Loading( type="circle", - children=dmc.Stack(id=ElementIds.TAB4_HEATMAP), + children=dmc.Stack(id=ElementIds.SUN_HEATMAP), ), ], ) @@ -187,6 +187,7 @@ def update_static_section(si_ip): ], [ Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_SUN_DF_STORE, "data"), @@ -194,9 +195,17 @@ def update_static_section(si_ip): State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) -def monthly_and_cloud_chart(_, df, meta, si_ip): +def monthly_and_cloud_chart(_, global_filter_data, df, meta, si_ip): """Update the contents of tab four. Passing in the polar selection and the general info (df, meta).""" + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data, + [Variables.GLOB_HOR_RAD.col_name, Variables.DIF_HOR_RAD.col_name, Variables.TOT_SKY_COVER.col_name]) + # Filter out the filtered rows for solar radiation calculations + if '_is_filtered' in df.columns: + df = df[~df['_is_filtered']] + # Sun Radiation monthly = monthly_solar(df, si_ip) monthly = monthly.update_layout(margin=tight_margins) @@ -210,9 +219,7 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): title="", legend=dict(orientation="h", yanchor="bottom", y=1.05, xanchor="right", x=1), ) - cover.update_xaxes( - dict(tickmode="array", tickvals=np.arange(0, 12, 1), ticktext=month_lst) - ) + # Remove the hardcoded x-axis update - let barchart handle it dynamically units = generate_units(si_ip) return dcc.Graph( style={"width": "100%", "height": "520px"}, @@ -234,6 +241,7 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): Input(ElementIds.CUSTOM_SUN_VIEW_DROPDOWN, "value"), Input(ElementIds.CUSTOM_SUN_VAR_DROPDOWN, "value"), Input(ElementIds.ID_SUN_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_SUN_DF_STORE, "data"), @@ -241,8 +249,27 @@ def monthly_and_cloud_chart(_, df, meta, si_ip): State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) -def sun_path_chart(_, view, var, global_local, df, meta, si_ip): +def sun_path_chart(_, view, var, global_local, global_filter_data, df, meta, si_ip): """Update the contents of tab four. Passing in the polar selection and the general info (df, meta).""" + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + # For sun path chart, we need to filter all sun position related columns + target_cols = [ + Variables.GLOB_HOR_RAD.col_name, + Variables.DIF_HOR_RAD.col_name, + Variables.APPARENT_ELEVATION.col_name, + Variables.APPARENT_ZENITH.col_name, + Variables.AZIMUTH.col_name, + Variables.ELEVATION.col_name, + Variables.DAY.col_name, + Variables.MONTH_NAMES.col_name, + Variables.HOUR.col_name + ] + # Add the selected variable if it's not "None" + if var != "None": + target_cols.append(var) + df = apply_global_month_hour_filter(df, global_filter_data, target_cols) + custom_inputs = "" if var == "None" else f"{var}" units = "" if var == "None" else generate_units(si_ip) if view == "polar": @@ -264,11 +291,12 @@ def sun_path_chart(_, view, var, global_local, df, meta, si_ip): @callback( - Output(ElementIds.TAB4_DAILY, "children"), + Output(ElementIds.SUN_DAILY, "children"), [ Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), - Input(ElementIds.TAB_EXPLORE_DROPDOWN, "value"), + Input(ElementIds.SUN_EXPLORE_DROPDOWN, "value"), Input(ElementIds.ID_SUN_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_SUN_DF_STORE, "data"), @@ -276,8 +304,12 @@ def sun_path_chart(_, view, var, global_local, df, meta, si_ip): State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) -def daily(_, var, global_local, df, meta, si_ip): +def daily(_, var, global_local, global_filter_data, df, meta, si_ip): """Update the contents of tab four section two. Passing in the general info (df, meta).""" + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data, var) + custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return dcc.Graph( @@ -288,11 +320,12 @@ def daily(_, var, global_local, df, meta, si_ip): @callback( - Output(ElementIds.TAB4_HEATMAP, "children"), + Output(ElementIds.SUN_HEATMAP, "children"), [ Input(ElementIds.ID_SUN_DF_STORE, "modified_timestamp"), - Input(ElementIds.TAB_EXPLORE_DROPDOWN, "value"), + Input(ElementIds.SUN_EXPLORE_DROPDOWN, "value"), Input(ElementIds.ID_SUN_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_SUN_DF_STORE, "data"), @@ -300,7 +333,11 @@ def daily(_, var, global_local, df, meta, si_ip): State(ElementIds.ID_SUN_SI_IP_UNIT_STORE, "data"), ], ) -def update_heatmap(_, var, global_local, df, meta, si_ip): +def update_heatmap(_, var, global_local, global_filter_data, df, meta, si_ip): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data, var) + custom_inputs = generate_custom_inputs(var) units = generate_units(si_ip) return dcc.Graph( diff --git a/pages/t_rh.py b/pages/t_rh.py index 932a9df1..676faccf 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -92,6 +92,7 @@ def layout(): Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), Input(ElementIds.ID_T_RH_DROPDOWN, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_T_RH_DF_STORE, "data"), @@ -99,7 +100,14 @@ def layout(): State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) -def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): +def update_yearly_chart(_, global_local, dd_value, global_filter_data, df, meta, si_ip): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + target_columns = [Variables.DBT.col_name, Variables.RH.col_name, Variables.ADAPTIVE_CMF_80_LOW.col_name, + Variables.ADAPTIVE_CMF_80_UP.col_name, Variables.ADAPTIVE_CMF_90_LOW.col_name, + Variables.ADAPTIVE_CMF_90_UP.col_name, Variables.ADAPTIVE_CMF_RMT.col_name] + df = apply_global_month_hour_filter(df, global_filter_data, target_columns) + if dd_value == dropdown_names[var_to_plot[0]]: dbt_yearly = yearly_profile(df, Variables.DBT.col_name, global_local, si_ip) dbt_yearly.update_layout(xaxis=dict(rangeslider=dict(visible=True))) @@ -126,6 +134,7 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), Input(ElementIds.ID_T_RH_DROPDOWN, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_T_RH_DF_STORE, "data"), @@ -133,7 +142,12 @@ def update_yearly_chart(_, global_local, dd_value, df, meta, si_ip): State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) -def update_daily(_, global_local, dd_value, df, meta, si_ip): +def update_daily(_, global_local, dd_value, global_filter_data, df, meta, si_ip): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + target_columns = [Variables.DBT.col_name, Variables.RH.col_name] + df = apply_global_month_hour_filter(df, global_filter_data, target_columns) + if dd_value == dropdown_names[var_to_plot[0]]: units = generate_units_degree(si_ip) return dcc.Graph( @@ -184,6 +198,7 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), Input(ElementIds.ID_T_RH_GLOBAL_LOCAL_RADIO_INPUT, "value"), Input(ElementIds.ID_T_RH_DROPDOWN, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_T_RH_DF_STORE, "data"), @@ -191,43 +206,40 @@ def update_daily(_, global_local, dd_value, df, meta, si_ip): State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) -def update_heatmap(_, global_local, dd_value, df, meta, si_ip): +def update_heatmap(_, global_local, dd_value, global_filter_data, df, meta, si_ip): """Update heatmap content.""" + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + target_columns = [Variables.DBT.col_name, Variables.RH.col_name] + df = apply_global_month_hour_filter(df, global_filter_data, target_columns) + + base_columns = [Variables.HOUR.col_name, Variables.UTC_TIME.col_name, Variables.MONTH_NAMES.col_name, Variables.DAY.col_name] + if '_is_filtered' in df.columns: + base_columns.append('_is_filtered') + if dd_value == dropdown_names[var_to_plot[0]]: + if f'_{Variables.DBT.col_name}_original' in df.columns: + base_columns.append(f'_{Variables.DBT.col_name}_original') units = generate_units_degree(si_ip) return dcc.Graph( config=generate_chart_name( TabNames.DRY_BULB_TEMPERATURE_HEATMAP, meta, units ), figure=heatmap( - df[ - [ - Variables.DBT.col_name, - Variables.HOUR.col_name, - Variables.UTC_TIME.col_name, - Variables.MONTH_NAMES.col_name, - Variables.DAY.col_name, - ] - ], + df[[Variables.DBT.col_name] + base_columns], Variables.DBT.col_name, global_local, si_ip, ), ) else: + if f'_{Variables.DBT.col_name}_original' in df.columns: + base_columns.append(f'_{Variables.DBT.col_name}_original') units = generate_units(si_ip) return dcc.Graph( config=generate_chart_name(TabNames.RELATIVE_HUMIDITY_HEATMAP, meta, units), figure=heatmap( - df[ - [ - Variables.RH.col_name, - Variables.HOUR.col_name, - Variables.UTC_TIME.col_name, - Variables.MONTH_NAMES.col_name, - Variables.DAY.col_name, - ] - ], + df[[Variables.DBT.col_name] + base_columns], Variables.RH.col_name, global_local, si_ip, @@ -240,14 +252,23 @@ def update_heatmap(_, global_local, dd_value, df, meta, si_ip): [ Input(ElementIds.ID_T_RH_DF_STORE, "modified_timestamp"), Input(ElementIds.ID_T_RH_DROPDOWN, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_T_RH_DF_STORE, "data"), State(ElementIds.ID_T_RH_SI_IP_UNIT_STORE, "data"), ], ) -def update_table(_, dd_value, df, si_ip): +def update_table(_, dd_value, global_filter_data, df, si_ip): """Update the contents of descriptive statistics table.""" + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + target_columns = [Variables.DBT.col_name, Variables.RH.col_name] + df = apply_global_month_hour_filter(df, global_filter_data, target_columns) + # Filter out the filtered rows to avoid empty columns + if '_is_filtered' in df.columns: + df = df[~df['_is_filtered']] + return summary_table_tmp_rh_tab( df[ [ diff --git a/pages/wind.py b/pages/wind.py index e9d0d9f6..937509c8 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -144,7 +144,7 @@ def seasonal_wind_rose(): def daily_wind_rose(): """Return the section for the 3 daily wind rose graphs.""" return dmc.Stack( - id=ElementIds.TAB5_DAILY_CONTAINER, + id=ElementIds.WIND_DAILY_CONTAINER, children=[ title_with_link( text="Daily Wind Rose", @@ -201,102 +201,6 @@ def daily_wind_rose(): ) -def custom_wind_rose(): - return dmc.Stack( - children=[ - title_with_tooltip( - text="Customizable Wind Rose", - tooltip_text=None, - id_button=IdButtons.CUSTOM_ROSE_CHART, - ), - dmc.Grid( - gutter="md", - maw=900, - mx="auto", - children=[ - dmc.GridCol( - span=6, - children=dmc.Stack( - children=[ - dmc.Group( - children=[ - dmc.Title( - "Start Month:", - order=5, - 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, - ), - ], - ), - ], - ), - ), - 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=dmc.Stack(id=ElementIds.CUSTOM_WIND_ROSE, maw=900, mx="auto"), - ), - ], - ) def layout(): @@ -323,22 +227,36 @@ def layout(): ), seasonal_wind_rose(), daily_wind_rose(), - custom_wind_rose(), ], ) @callback( Output(ElementIds.WIND_ROSE, "children"), - Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), + [ + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), + ], [ State(ElementIds.ID_WIND_DF_STORE, "data"), State(ElementIds.ID_WIND_META_STORE, "data"), State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) -def update_annual_wind_rose(_, df, meta, si_ip): - annual = wind_rose(df, "", [1, 12], [1, 24], True, si_ip) +def update_annual_wind_rose(_, global_filter_data, df, meta, si_ip): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + df = apply_global_month_hour_filter(df, global_filter_data, [Variables.WIND_SPEED.col_name, Variables.WIND_DIR.col_name]) + + months = [1, 12] + hours = [1, 24] + else: + months = [1, 12] + hours = [1, 24] + + skip_filter = global_filter_data and global_filter_data.get("filter_active", False) + annual = wind_rose(df, "", months, hours, True, si_ip, skip_time_filter=skip_filter) + units = generate_units(si_ip) return dcc.Graph( config=generate_chart_name(TabNames.ANNUAL_WIND_ROSE, meta, units), @@ -351,6 +269,7 @@ def update_annual_wind_rose(_, df, meta, si_ip): [ Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), ], [ State(ElementIds.ID_WIND_DF_STORE, "data"), @@ -358,7 +277,11 @@ def update_annual_wind_rose(_, df, meta, si_ip): State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) -def update_tab_wind_speed(_, global_local, df, meta, si_ip): +def update_tab_wind_speed(_, global_local, global_filter_data, df, meta, si_ip): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data, Variables.WIND_SPEED.col_name) + speed = heatmap(df, Variables.WIND_SPEED.col_name, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( @@ -369,14 +292,22 @@ def update_tab_wind_speed(_, global_local, df, meta, si_ip): @callback( Output(ElementIds.WIND_DIRECTION, "children"), - [Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value")], + [ + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), + Input(ElementIds.ID_WIND_GLOBAL_LOCAL_RADIO_INPUT, "value"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), + ], [ State(ElementIds.ID_WIND_DF_STORE, "data"), State(ElementIds.ID_WIND_META_STORE, "data"), State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) -def update_tab_wind_direction(global_local, df, meta, si_ip): +def update_tab_wind_direction(_, global_local, global_filter_data, df, meta, si_ip): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data, Variables.WIND_DIR.col_name) + direction = heatmap(df, Variables.WIND_DIR.col_name, global_local, si_ip) units = generate_units(si_ip) return dcc.Graph( @@ -385,63 +316,6 @@ def update_tab_wind_direction(global_local, df, meta, si_ip): ) -@callback( - Output(ElementIds.CUSTOM_WIND_ROSE, "children"), - [ - Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), - Input(ElementIds.TAB5_CUSTOM_START_MONTH, "value"), - Input(ElementIds.TAB5_CUSTOM_START_HOUR, "value"), - Input(ElementIds.TAB5_CUSTOM_END_MONTH, "value"), - Input(ElementIds.TAB5_CUSTOM_END_HOUR, "value"), - ], - [ - State(ElementIds.ID_WIND_DF_STORE, "data"), - State(ElementIds.ID_WIND_META_STORE, "data"), - State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), - ], -) -def update_custom_wind_rose( - _, start_month, start_hour, end_month, end_hour, df, meta, si_ip -): - start_hour = int(start_hour) - end_hour = int(end_hour) - start_month = int(start_month) - end_month = int(end_month) - - if start_month <= end_month: - df = df.loc[ - (df[Variables.MONTH.col_name] >= start_month) - & (df[Variables.MONTH.col_name] <= end_month) - ] - else: - df = df.loc[ - (df[Variables.MONTH.col_name] <= end_month) - | (df[Variables.MONTH.col_name] >= start_month) - ] - if start_hour <= end_hour: - df = df.loc[ - (df[Variables.HOUR.col_name] >= start_hour) - & (df[Variables.HOUR.col_name] <= end_hour) - ] - else: - df = df.loc[ - (df[Variables.HOUR.col_name] <= end_hour) - | (df[Variables.HOUR.col_name] >= start_hour) - ] - - custom = wind_rose( - df, "", [start_month, end_month], [start_hour, end_hour], True, si_ip - ) - custom_inputs = generate_custom_inputs_time( - start_month, end_month, start_hour, end_hour - ) - units = generate_units(si_ip) - return dcc.Graph( - config=generate_chart_name( - TabNames.CUSTOM_WIND_ROSE, meta, custom_inputs, units - ), - figure=custom, - ) @callback( @@ -571,15 +445,25 @@ 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"), ], - Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), + [ + Input(ElementIds.ID_WIND_DF_STORE, "modified_timestamp"), + Input(ElementIds.TOOLS_GLOBAL_FILTER_STORE, "data"), + ], [ State(ElementIds.ID_WIND_DF_STORE, "data"), State(ElementIds.ID_WIND_META_STORE, "data"), State(ElementIds.ID_WIND_SI_IP_UNIT_STORE, "data"), ], ) -def update_daily_graphs(_, df, meta, si_ip): - months = [1, 12] +def update_daily_graphs(_, global_filter_data, df, meta, si_ip): + if global_filter_data and global_filter_data.get("filter_active", False): + from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + df = apply_global_month_hour_filter(df, global_filter_data, [Variables.WIND_SPEED.col_name, Variables.WIND_DIR.col_name]) + + months = [1, 12] + else: + months = [1, 12] + morning_times = [6, 13] noon_times = [14, 21] night_times = [22, 5] diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js index 3de4a4aa..f0ea1ae6 100644 --- a/tests/node/cypress/e2e/spec.cy.js +++ b/tests/node/cypress/e2e/spec.cy.js @@ -84,8 +84,6 @@ describe('Clima', () => { // TODO cy.contains('Daily Wind Rose'); // TODO - cy.contains('Customizable Wind Rose'); - // TODO // Psychrometric Chart click_tab('Psychrometric Chart'); @@ -102,7 +100,7 @@ describe('Clima', () => { // Outdoor Comfort click_tab('Outdoor Comfort'); // TODO - cy.contains('The Best Weather Condition is: utci_noSun_noWind_categories'); + cy.contains('The Best Weather Condition is: UTCI No Sun No Wind Categories'); // TODO cy.contains('UTCI thermal stress chart'); cy.contains('no thermal stress'); From bde8f30b2c2648d44525b9c2d3bdab1f38ffd4b5 Mon Sep 17 00:00:00 2001 From: yluo0664 Date: Mon, 27 Oct 2025 21:15:18 +1100 Subject: [PATCH 117/163] fix: updated the documentation of Contributor, Code Style ,and Pull Request Regulation --- docs/README.md | 6 ++++++ docs/contributing/contributing.md | 22 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/docs/README.md b/docs/README.md index 05ce6e22..b194b929 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,6 +34,12 @@ This ongoing project results from the collaboration and contributions of the peo * [Chun Him Lee](https://www.linkedin.com/in/chun-him-lee-01b553129/): Coding and review * [Tu Minh Phuong Doan](https://www.linkedin.com/in/harry-doan-legopher/): Coding and review * [Yixun Quan](https://www.linkedin.com/in/yixun-quan-929a661a3): Coding and review +* [Yuqing Luo](https://github.com/LeoLuosifen): Coding, code maintenance and review +* Wenshu lyu: Coding, code maintenance and review +* Ziqi Liu: Coding, code maintenance and review +* Tianchi Liu: Coding, code maintenance and review +* Qian Liu: Coding, code maintenance and review +* Feng Wang: Coding, code maintenance and review ## Acknowledgment diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 103d52f0..3bc89210 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -77,6 +77,7 @@ Available [here](code_of_conduct.md) ## Code style +### Code Formatting We use ruff to enforce the code style and code formatting. You can run it with: ```bash @@ -108,6 +109,19 @@ Format your code before committing: black . ``` +### Code Simplicity +Strive to minimize redundancy in your code. +- Keep logic as concise as possible. +- Remove unused variables, imports, and components. +- Prefer reusable utilities to repeated patterns. + +### UI Modifications +For UI-related changes: +* Avoid unnecessary custom CSS. +* [Use DMC (Design Master Components)](https://www.dash-mantine-components.com/) wherever applicable for layout and styling consistency. +* Ensure visual consistency with existing components. + + ## Testing Before submitting a Pull Request, please make sure: @@ -172,6 +186,14 @@ Classification of Common Commit Types: - **Testing:** Describe how you tested your changes and how we can reproduce them. Include test details if necessary. +**Pull Request Review:** + +- After submitting a Pull Request (PR), please @Coderabbit for review. +- Check all improvement suggestions provided by Coderabbit before requesting a final review. + +**Discussion of Solutions:** + When needed, seek feedback from collaborators Toby and Giobetti before making major design or logic decisions. + ## Thanks From 18a0361d8d213620ad052afec25465ce5ae3033a Mon Sep 17 00:00:00 2001 From: yluo0664 Date: Mon, 27 Oct 2025 21:15:59 +1100 Subject: [PATCH 118/163] =?UTF-8?q?fix:=20deleted=20unnecessary=20style=20?= =?UTF-8?q?in=20css=20files=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/banner.css | 8 -------- assets/fonts.css | 5 ----- assets/layout.css | 31 ------------------------------- 3 files changed, 44 deletions(-) delete mode 100644 assets/fonts.css delete mode 100644 assets/layout.css diff --git a/assets/banner.css b/assets/banner.css index edba1bce..ef73f96a 100644 --- a/assets/banner.css +++ b/assets/banner.css @@ -1,12 +1,4 @@ /**Banner**/ -#banner { - padding: 1rem; - background-color: #003262; - color: white; - /* position: sticky; */ - z-index: 1; -} - #banner-title { font-family: 'Open Sans', sans-serif; font-weight: 500; diff --git a/assets/fonts.css b/assets/fonts.css deleted file mode 100644 index 61d71fb3..00000000 --- a/assets/fonts.css +++ /dev/null @@ -1,5 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap'); - -@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;400&display=swap'); - -@import url('https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300;0,400;0,600;0,700;0,800;1,300;1,400;1,600;1,700;1,800&display=swap'); \ No newline at end of file diff --git a/assets/layout.css b/assets/layout.css deleted file mode 100644 index 42ed6cb2..00000000 --- a/assets/layout.css +++ /dev/null @@ -1,31 +0,0 @@ -.container-col { - display: flex; - flex-direction: column; -} - -.container-row { - display: flex; - flex-direction: row; -} - -.justify-center { - justify-content: center; -} - -.align-center{ - align-items: center; -} - -.container-stretch { - align-items: stretch; -} - -.full-width { - width: 100%; -} - -.doc-modal-body img { - box-sizing: border-box; - width: 100%; //or any percentage width you want -} - From a4742e899d0d55f4fe457f284648f4609c8a1870 Mon Sep 17 00:00:00 2001 From: yluo0664 Date: Mon, 27 Oct 2025 21:32:57 +1100 Subject: [PATCH 119/163] fix: formatted the code. --- pages/explorer.py | 30 +++- pages/lib/charts_data_explorer.py | 132 ++++++++------- pages/lib/global_element_ids.py | 2 +- pages/lib/layout.py | 65 ++++--- pages/lib/template_graphs.py | 270 +++++++++++++++++------------- pages/natural_ventilation.py | 21 ++- pages/outdoor.py | 26 ++- pages/psy-chart.py | 10 +- pages/select.py | 10 +- pages/summary.py | 9 +- pages/sun.py | 21 ++- pages/t_rh.py | 39 +++-- pages/wind.py | 38 +++-- 13 files changed, 409 insertions(+), 264 deletions(-) diff --git a/pages/explorer.py b/pages/explorer.py index 62251757..fb398996 100644 --- a/pages/explorer.py +++ b/pages/explorer.py @@ -390,9 +390,15 @@ def update_tab_yearly(_, var, global_local, global_filter_data, df, meta, si_ip) if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter - target_columns = [var, Variables.ADAPTIVE_CMF_80_LOW.col_name, Variables.ADAPTIVE_CMF_80_UP.col_name, - Variables.ADAPTIVE_CMF_90_LOW.col_name, Variables.ADAPTIVE_CMF_90_UP.col_name, - Variables.ADAPTIVE_CMF_RMT.col_name] + + target_columns = [ + var, + Variables.ADAPTIVE_CMF_80_LOW.col_name, + Variables.ADAPTIVE_CMF_80_UP.col_name, + Variables.ADAPTIVE_CMF_90_LOW.col_name, + Variables.ADAPTIVE_CMF_90_UP.col_name, + Variables.ADAPTIVE_CMF_RMT.col_name, + ] df = apply_global_month_hour_filter(df, global_filter_data, target_columns) if df[var].mean() == 99990.0: @@ -431,6 +437,7 @@ def update_tab_daily(_, var, global_local, global_filter_data, df, meta, si_ip): """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data) custom_inputs = generate_custom_inputs(var) @@ -464,6 +471,7 @@ def update_tab_heatmap(_, var, global_local, global_filter_data, df, meta, si_ip """Update the contents of tab size. Passing in the info from the dropdown and the general info.""" if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data) custom_inputs = generate_custom_inputs(var) @@ -518,7 +526,11 @@ def update_heatmap( si_ip, ): if global_filter_data and global_filter_data.get("filter_active", False): - from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + from pages.lib.layout import ( + apply_global_month_hour_filter, + get_global_filter_state, + ) + df = apply_global_month_hour_filter(df, global_filter_data, var) filter_state = get_global_filter_state(global_filter_data) @@ -641,12 +653,11 @@ def update_more_charts( if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data) else: # Use local filtering when global filter is not active - df = filter_df_by_month_and_hour( - df, True, [1, 12], [0, 24], [], [], df.columns - ) + df = filter_df_by_month_and_hour(df, True, [1, 12], [0, 24], [], [], df.columns) data_filter_info = [data_filter, data_filter_var, min_val, max_val] if data_filter and (min_val is None or max_val is None): @@ -715,10 +726,11 @@ def update_table( ): if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + filtered_df = apply_global_month_hour_filter(df, global_filter_data) # Filter out the filtered rows to avoid empty columns - if '_is_filtered' in filtered_df.columns: - filtered_df = filtered_df[~filtered_df['_is_filtered']] + if "_is_filtered" in filtered_df.columns: + filtered_df = filtered_df[~filtered_df["_is_filtered"]] else: # Use default values when global filter is not active filtered_df = df diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index cfee251c..c63738b9 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -62,12 +62,12 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si fig = go.Figure() - has_filter_marker = '_is_filtered' in df.columns + has_filter_marker = "_is_filtered" in df.columns - if has_filter_marker and df['_is_filtered'].any(): - filtered_mask = df['_is_filtered'] + if has_filter_marker and df["_is_filtered"].any(): + filtered_mask = df["_is_filtered"] if filtered_mask.any(): - original_col = f'_{var}_original' + original_col = f"_{var}_original" if original_col in df.columns: filtered_z = df[original_col].copy() else: @@ -75,40 +75,48 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si filtered_z[~filtered_mask] = None - fig.add_trace(go.Heatmap( - y=df[Variables.HOUR.col_name], - x=df[Variables.DOY.col_name], - z=filtered_z, - colorscale=[[0, 'lightgray'], [1, 'gray']], - zmin=range_z[0], - zmax=range_z[1], - showscale=False, - connectgaps=False, - hoverongaps=False, - customdata=np.stack((df[Variables.MONTH.col_name], df[Variables.DAY.col_name]), axis=-1), - hovertemplate=( + fig.add_trace( + go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.DOY.col_name], + z=filtered_z, + colorscale=[[0, "lightgray"], [1, "gray"]], + zmin=range_z[0], + zmax=range_z[1], + showscale=False, + connectgaps=False, + hoverongaps=False, + customdata=np.stack( + (df[Variables.MONTH.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), + hovertemplate=( "Filtered Data
" + "Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" - " %{y}:00
" - ), - name="filtered", - )) + " %{y}:00
" + ), + name="filtered", + ) + ) normal_mask = ~filtered_mask normal_z = df[var].copy() normal_z[filtered_mask] = None - fig.add_trace(go.Heatmap( - y=df[Variables.HOUR.col_name], - x=df[Variables.DOY.col_name], - z=normal_z, - colorscale=var_color, - zmin=range_z[0], - zmax=range_z[1], - connectgaps=False, - hoverongaps=False, - customdata=np.stack((df[Variables.MONTH.col_name], df[Variables.DAY.col_name]), axis=-1), - hovertemplate=( + fig.add_trace( + go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.DOY.col_name], + z=normal_z, + colorscale=var_color, + zmin=range_z[0], + zmax=range_z[1], + connectgaps=False, + hoverongaps=False, + customdata=np.stack( + (df[Variables.MONTH.col_name], df[Variables.DAY.col_name]), axis=-1 + ), + hovertemplate=( "" + var + ": %{z:.2f} " @@ -117,38 +125,40 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si + "Month: %{customdata[0]}
" + "Day: %{customdata[1]}
" + "Hour: %{y}:00
" - ), - name="", - colorbar=dict(title=var_unit), - )) + ), + name="", + colorbar=dict(title=var_unit), + ) + ) else: - fig.add_trace(go.Heatmap( - y=df[Variables.HOUR.col_name], - x=df[Variables.DOY.col_name], - z=df[var], - colorscale=var_color, - zmin=range_z[0], - zmax=range_z[1], - connectgaps=False, - hoverongaps=False, - customdata=np.stack( - (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), - axis=-1, - ), - hovertemplate=( - "" - + var - + ": %{z:.2f} " - + var_unit - + "
" - + "Month: %{customdata[0]}
" - + "Day: %{customdata[1]}
" - + "Hour: %{y}:00
" - ), - name="", - colorbar=dict(title=var_unit), + fig.add_trace( + go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.DOY.col_name], + z=df[var], + colorscale=var_color, + zmin=range_z[0], + zmax=range_z[1], + connectgaps=False, + hoverongaps=False, + customdata=np.stack( + (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), + hovertemplate=( + "" + + var + + ": %{z:.2f} " + + var_unit + + "
" + + "Month: %{customdata[0]}
" + + "Day: %{customdata[1]}
" + + "Hour: %{y}:00
" + ), + name="", + colorbar=dict(title=var_unit), + ) ) - ) fig.update_layout( template=template, title=title, diff --git a/pages/lib/global_element_ids.py b/pages/lib/global_element_ids.py index c88b3df8..67af4dab 100644 --- a/pages/lib/global_element_ids.py +++ b/pages/lib/global_element_ids.py @@ -241,4 +241,4 @@ class ElementIds(str, Enum): TOOLS_INVERT_HOUR = "tools-invert-hour" TOOLS_FILTER_SECTION = "tools-filter-section" TOOLS_MONTH_HOUR_SECTION = "tools-month-hour-section" - TOOLS_GLOBAL_FILTER_STORE = "tools-global-filter-store" \ No newline at end of file + TOOLS_GLOBAL_FILTER_STORE = "tools-global-filter-store" diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 7cfc014f..bb116dca 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -38,15 +38,14 @@ def get_icon(cls, page_name): """Get icon for a page name.""" return cls._ICON_MAP.get(page_name, "tabler:circle") + # global filters def create_tools_filter_components(): # Apply month and hour filter apply_month_hour_section = dmc.Stack( id=ElementIds.TOOLS_MONTH_HOUR_SECTION, children=[ - dmc.Divider( - label="Filter function", size="xs", color="blue", mb="xs" - ), + dmc.Divider(label="Filter function", size="xs", color="blue", mb="xs"), dmc.Button( "Apply month and hour filter", id=ElementIds.TOOLS_APPLY_MONTH_HOUR_FILTER, @@ -133,7 +132,11 @@ def create_tools_filter_components(): ], gap="xs", p="xs", - style={"backgroundColor": "#f8f9fa", "borderRadius": "6px", "border": "1px solid #e9ecef"}, + style={ + "backgroundColor": "#f8f9fa", + "borderRadius": "6px", + "border": "1px solid #e9ecef", + }, ) return dmc.Stack( @@ -143,6 +146,7 @@ def create_tools_filter_components(): gap="sm", ) + def create_navbar(): nav_link_styles = { "root": { @@ -431,9 +435,9 @@ def create_stores(): "hour_range": [0, 24], "invert_month": [], "invert_hour": [], - "filter_active": False + "filter_active": False, }, - storage_type="session" + storage_type="session", ), dcc.Interval( id=ElementIds.ID_LAYOUT_INTERVAL_COMPONENT, @@ -561,7 +565,7 @@ def show_alert_after_delay(n_intervals): prevent_initial_call=True, ) def update_global_filter_state( - apply_clicks, month_range, hour_range, invert_month, invert_hour, current_data + apply_clicks, month_range, hour_range, invert_month, invert_hour, current_data ): if apply_clicks is None: apply_clicks = 0 @@ -569,12 +573,14 @@ def update_global_filter_state( if apply_clicks > 0: current_data["filter_active"] = True - current_data.update({ - "month_range": month_range or [1, 12], - "hour_range": hour_range or [0, 24], - "invert_month": ["invert"] if invert_month else [], - "invert_hour": ["invert"] if invert_hour else [], - }) + current_data.update( + { + "month_range": month_range or [1, 12], + "hour_range": hour_range or [0, 24], + "invert_month": ["invert"] if invert_month else [], + "invert_hour": ["invert"] if invert_hour else [], + } + ) return current_data @@ -603,7 +609,7 @@ def apply_global_month_hour_filter(df, filter_store_data, target_columns=None): if not filter_state["filter_active"]: df_copy = df.copy() - df_copy['_is_filtered'] = False + df_copy["_is_filtered"] = False return df_copy month_range = filter_state["month_range"] @@ -624,24 +630,37 @@ def apply_global_month_hour_filter(df, filter_store_data, target_columns=None): month_mask = None if start_month <= end_month: - month_mask = (df_copy[Variables.MONTH.col_name] < start_month) | (df_copy[Variables.MONTH.col_name] > end_month) + month_mask = (df_copy[Variables.MONTH.col_name] < start_month) | ( + df_copy[Variables.MONTH.col_name] > end_month + ) else: - month_mask = (df_copy[Variables.MONTH.col_name] >= end_month) & (df_copy[Variables.MONTH.col_name] <= start_month) + month_mask = (df_copy[Variables.MONTH.col_name] >= end_month) & ( + df_copy[Variables.MONTH.col_name] <= start_month + ) hour_mask = None if start_hour <= end_hour: - hour_mask = (df_copy[Variables.HOUR.col_name] < start_hour) | (df_copy[Variables.HOUR.col_name] > end_hour) + hour_mask = (df_copy[Variables.HOUR.col_name] < start_hour) | ( + df_copy[Variables.HOUR.col_name] > end_hour + ) else: - hour_mask = (df_copy[Variables.HOUR.col_name] >= end_hour) & (df_copy[Variables.HOUR.col_name] <= start_hour) + hour_mask = (df_copy[Variables.HOUR.col_name] >= end_hour) & ( + df_copy[Variables.HOUR.col_name] <= start_hour + ) - df_copy['_is_filtered'] = month_mask | hour_mask + df_copy["_is_filtered"] = month_mask | hour_mask for target_col in target_columns: - df_copy[f'_{target_col}_original'] = df_copy[target_col] + df_copy[f"_{target_col}_original"] = df_copy[target_col] from pages.lib.template_graphs import time_filtering - time_filtering(df_copy, start_month, end_month, Variables.MONTH.col_name, target_col) - time_filtering(df_copy, start_hour, end_hour, Variables.MONTH.col_name, target_col) + + time_filtering( + df_copy, start_month, end_month, Variables.MONTH.col_name, target_col + ) + time_filtering( + df_copy, start_hour, end_hour, Variables.MONTH.col_name, target_col + ) return df_copy @@ -668,5 +687,3 @@ def sync_sliders_with_global_state(global_filter_data): bool(global_filter_data.get("invert_month", [])), bool(global_filter_data.get("invert_hour", [])), ) - - diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 633fc3ae..5db67b8f 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -378,17 +378,17 @@ def heatmap_with_filter( var_range = variable.get_range(si_ip) var_color = variable.get_color() - has_global_filter_marker = '_is_filtered' in df.columns + has_global_filter_marker = "_is_filtered" in df.columns global_filter_mask = None if has_global_filter_marker: - global_filter_mask = df['_is_filtered'].copy() + global_filter_mask = df["_is_filtered"].copy() df = filter_df_by_month_and_hour( df, time_filter, month, hour, invert_month, invert_hour, var ) if has_global_filter_marker and global_filter_mask is not None: - df['_is_filtered'] = global_filter_mask + df["_is_filtered"] = global_filter_mask start_month, end_month, start_hour, end_hour = determine_month_and_hour_filter( month, hour, invert_month, invert_hour @@ -414,12 +414,12 @@ def heatmap_with_filter( range_z = [data_min, data_max] fig = go.Figure() - has_filter_marker = '_is_filtered' in df.columns + has_filter_marker = "_is_filtered" in df.columns - if has_filter_marker and df['_is_filtered'].any(): - filtered_mask = df['_is_filtered'] + if has_filter_marker and df["_is_filtered"].any(): + filtered_mask = df["_is_filtered"] if filtered_mask.any(): - original_col = f'_{var}_original' + original_col = f"_{var}_original" if original_col in df.columns: filtered_z = df[original_col].copy() else: @@ -427,71 +427,89 @@ def heatmap_with_filter( filtered_z[~filtered_mask] = None - fig.add_trace(go.Heatmap( - y=df[Variables.HOUR.col_name] - 0.5, - x=df[Variables.UTC_TIME.col_name].dt.date, - z=filtered_z, - colorscale=[[0, 'lightgray'], [1, 'gray']], - zmin=range_z[0], - zmax=range_z[1], - showscale=False, - customdata=np.stack((df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), axis=-1), - hovertemplate=( + fig.add_trace( + go.Heatmap( + y=df[Variables.HOUR.col_name] - 0.5, + x=df[Variables.UTC_TIME.col_name].dt.date, + z=filtered_z, + colorscale=[[0, "lightgray"], [1, "gray"]], + zmin=range_z[0], + zmax=range_z[1], + showscale=False, + customdata=np.stack( + ( + df[Variables.MONTH_NAMES.col_name], + df[Variables.DAY.col_name], + ), + axis=-1, + ), + hovertemplate=( "Filtered Data
" + "Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" - " %{y}:00
" - ), - name="filtered", - )) + " %{y}:00
" + ), + name="filtered", + ) + ) normal_mask = ~filtered_mask normal_z = df[var].copy() normal_z[filtered_mask] = None - fig.add_trace(go.Heatmap( - y=df[Variables.HOUR.col_name] - 0.5, - x=df[Variables.UTC_TIME.col_name].dt.date, - z=normal_z, - colorscale=var_color, - zmin=range_z[0], - zmax=range_z[1], - customdata=np.stack((df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), axis=-1), - hovertemplate=( + fig.add_trace( + go.Heatmap( + y=df[Variables.HOUR.col_name] - 0.5, + x=df[Variables.UTC_TIME.col_name].dt.date, + z=normal_z, + colorscale=var_color, + zmin=range_z[0], + zmax=range_z[1], + customdata=np.stack( + (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), + hovertemplate=( "" + var + ": %{z:.2f} " + var_unit + "
Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" - " %{y}:00
" - ), - name="", - colorbar=dict(title="") if "_categories" in var else dict(title=var_unit), - )) + " %{y}:00
" + ), + name="", + colorbar=( + dict(title="") if "_categories" in var else dict(title=var_unit) + ), + ) + ) else: - fig.add_trace(go.Heatmap( - y=df[Variables.HOUR.col_name] - - 0.5, # Offset by 0.5 to center the hour labels - x=df[Variables.UTC_TIME.col_name].dt.date, - z=df[var], - colorscale=var_color, - zmin=range_z[0], - zmax=range_z[1], - customdata=np.stack( - (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), - axis=-1, - ), - hovertemplate=( - "" - + var - + ": %{z:.2f} " - + var_unit - + "
Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" - " %{y}:00
" - ), - name="", - colorbar=dict(title="") if "_categories" in var else dict(title=var_unit), + fig.add_trace( + go.Heatmap( + y=df[Variables.HOUR.col_name] + - 0.5, # Offset by 0.5 to center the hour labels + x=df[Variables.UTC_TIME.col_name].dt.date, + z=df[var], + colorscale=var_color, + zmin=range_z[0], + zmax=range_z[1], + customdata=np.stack( + (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), + hovertemplate=( + "" + + var + + ": %{z:.2f} " + + var_unit + + "
Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" + " %{y}:00
" + ), + name="", + colorbar=( + dict(title="") if "_categories" in var else dict(title=var_unit) + ), + ) ) - ) if var == Variables.WIND_SPEED.col_name: spd_bins = list(WIND_ROSE_BINS) @@ -538,12 +556,12 @@ def heatmap(df, var, global_local, si_ip): range_z = [data_min, data_max] fig = go.Figure() - has_filter_marker = '_is_filtered' in df.columns + has_filter_marker = "_is_filtered" in df.columns - if has_filter_marker and df['_is_filtered'].any(): - filtered_mask = df['_is_filtered'] + if has_filter_marker and df["_is_filtered"].any(): + filtered_mask = df["_is_filtered"] if filtered_mask.any(): - original_col = f'_{var}_original' + original_col = f"_{var}_original" if original_col in df.columns: filtered_z = df[original_col].copy() else: @@ -551,70 +569,84 @@ def heatmap(df, var, global_local, si_ip): filtered_z[~filtered_mask] = None - fig.add_trace(go.Heatmap( - y=df[Variables.HOUR.col_name], - x=df[Variables.UTC_TIME.col_name].dt.date, - z=filtered_z, - colorscale=[[0, 'lightgray'], [1, 'gray']], - zmin=range_z[0], - zmax=range_z[1], - showscale=False, - customdata=np.stack((df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), axis=-1), - hovertemplate=( + fig.add_trace( + go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.UTC_TIME.col_name].dt.date, + z=filtered_z, + colorscale=[[0, "lightgray"], [1, "gray"]], + zmin=range_z[0], + zmax=range_z[1], + showscale=False, + customdata=np.stack( + ( + df[Variables.MONTH_NAMES.col_name], + df[Variables.DAY.col_name], + ), + axis=-1, + ), + hovertemplate=( "Filtered Data
" + "Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" - " %{y}:00
" - ), - name="filtered", - )) + " %{y}:00
" + ), + name="filtered", + ) + ) normal_mask = ~filtered_mask normal_z = df[var].copy() normal_z[filtered_mask] = None - fig.add_trace(go.Heatmap( - y=df[Variables.HOUR.col_name], - x=df[Variables.UTC_TIME.col_name].dt.date, - z=normal_z, - colorscale=var_color, - zmin=range_z[0], - zmax=range_z[1], - customdata=np.stack((df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), axis=-1), - hovertemplate=( + fig.add_trace( + go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.UTC_TIME.col_name].dt.date, + z=normal_z, + colorscale=var_color, + zmin=range_z[0], + zmax=range_z[1], + customdata=np.stack( + (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), + hovertemplate=( "" + var + ": %{z:.2f} " + var_unit + "
Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" - " %{y}:00
" - ), - name="", - colorbar=dict(title=var_unit), - )) + " %{y}:00
" + ), + name="", + colorbar=dict(title=var_unit), + ) + ) else: - fig.add_trace(go.Heatmap( - y=df[Variables.HOUR.col_name], - x=df[Variables.UTC_TIME.col_name].dt.date, - z=df[var], - colorscale=var_color, - zmin=range_z[0], - zmax=range_z[1], - customdata=np.stack( - (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), - axis=-1, - ), - hovertemplate=( - "" - + var - + ": %{z:.2f} " - + var_unit - + "
Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" - " %{y}:00
" - ), - name="", - colorbar=dict(title=var_unit), + fig.add_trace( + go.Heatmap( + y=df[Variables.HOUR.col_name], + x=df[Variables.UTC_TIME.col_name].dt.date, + z=df[var], + colorscale=var_color, + zmin=range_z[0], + zmax=range_z[1], + customdata=np.stack( + (df[Variables.MONTH_NAMES.col_name], df[Variables.DAY.col_name]), + axis=-1, + ), + hovertemplate=( + "" + + var + + ": %{z:.2f} " + + var_unit + + "
Month: %{customdata[0]}
Day: %{customdata[1]}
Hour:" + " %{y}:00
" + ), + name="", + colorbar=dict(title=var_unit), + ) ) - ) if var == Variables.WIND_SPEED.col_name: spd_bins = list(WIND_ROSE_BINS) @@ -907,7 +939,11 @@ def thermal_stress_stacked_barchart( available_months = sorted(new_df[Variables.MONTH.col_name].unique()) fig.update_xaxes( - dict(tickmode="array", tickvals=np.arange(0, len(available_months), 1), ticktext=month_lst), + dict( + tickmode="array", + tickvals=np.arange(0, len(available_months), 1), + ticktext=month_lst, + ), title_text="Day", showline=True, linewidth=1, @@ -958,9 +994,7 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): for month_num in range(1, 13): if month_num in available_months_set: - query = ( - f"month=={str(month_num)} and ({filter_var}>={min_val} and {filter_var}<={max_val})" - ) + query = f"month=={str(month_num)} and ({filter_var}>={min_val} and {filter_var}<={max_val})" a = new_df.query(query)[Variables.DOY.col_name].count() month_in.append(a) query = f"month=={str(month_num)} and ({filter_var}<{min_val})" @@ -979,9 +1013,7 @@ def barchart(df, var, time_filter_info, data_filter_info, normalize, si_ip): month_names = month_lst - trace1 = go.Bar( - x=month_names, y=month_in, name="IN range", marker_color=color_in - ) + trace1 = go.Bar(x=month_names, y=month_in, name="IN range", marker_color=color_in) trace2 = go.Bar( x=month_names, y=month_below, diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index 17499644..e97181ef 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -224,8 +224,14 @@ def nv_heatmap( dpt_data_filter = enable_dew_point_data_filter(condensation_enabled) if global_filter_data and global_filter_data.get("filter_active", False): - from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state - df = apply_global_month_hour_filter(df, global_filter_data, Variables.DBT.col_name) + from pages.lib.layout import ( + apply_global_month_hour_filter, + get_global_filter_state, + ) + + df = apply_global_month_hour_filter( + df, global_filter_data, Variables.DBT.col_name + ) filter_state = get_global_filter_state(global_filter_data) month_range = filter_state["month_range"] @@ -264,7 +270,6 @@ def nv_heatmap( ), ) - variable = VariableInfo.from_col_name(var) filter = VariableInfo.from_col_name(filter_var) @@ -419,8 +424,14 @@ def nv_bar_chart( df[Variables.NV_ALLOWED.col_name] = 1 if global_filter_data and global_filter_data.get("filter_active", False): - from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state - df = apply_global_month_hour_filter(df, global_filter_data, Variables.NV_ALLOWED.col_name) + from pages.lib.layout import ( + apply_global_month_hour_filter, + get_global_filter_state, + ) + + df = apply_global_month_hour_filter( + df, global_filter_data, Variables.NV_ALLOWED.col_name + ) filter_state = get_global_filter_state(global_filter_data) month_range = filter_state["month_range"] diff --git a/pages/outdoor.py b/pages/outdoor.py index 538041c4..2b96697b 100644 --- a/pages/outdoor.py +++ b/pages/outdoor.py @@ -218,7 +218,11 @@ def update_tab_utci_value( si_ip, ): if global_filter_data and global_filter_data.get("filter_active", False): - from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + from pages.lib.layout import ( + apply_global_month_hour_filter, + get_global_filter_state, + ) + df = apply_global_month_hour_filter(df, global_filter_data, var) filter_state = get_global_filter_state(global_filter_data) @@ -301,8 +305,14 @@ def update_tab_utci_category( si_ip, ): if global_filter_data and global_filter_data.get("filter_active", False): - from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state - df = apply_global_month_hour_filter(df, global_filter_data, [var, var + "_categories"]) + from pages.lib.layout import ( + apply_global_month_hour_filter, + get_global_filter_state, + ) + + df = apply_global_month_hour_filter( + df, global_filter_data, [var, var + "_categories"] + ) filter_state = get_global_filter_state(global_filter_data) month_range = filter_state["month_range"] @@ -380,11 +390,13 @@ def update_tab_utci_category( State(ElementIds.ID_OUTDOOR_SI_IP_UNIT_STORE, "data"), ], ) -def update_tab_utci_summary_chart( - var, normalize, global_filter_data, df, meta, si_ip -): +def update_tab_utci_summary_chart(var, normalize, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): - from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + from pages.lib.layout import ( + apply_global_month_hour_filter, + get_global_filter_state, + ) + df = apply_global_month_hour_filter(df, global_filter_data, var) filter_state = get_global_filter_state(global_filter_data) diff --git a/pages/psy-chart.py b/pages/psy-chart.py index 48a1ae4f..fa5fdcea 100644 --- a/pages/psy-chart.py +++ b/pages/psy-chart.py @@ -185,7 +185,11 @@ def update_psych_chart( si_ip, ): if global_filter_data and global_filter_data.get("filter_active", False): - from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state + from pages.lib.layout import ( + apply_global_month_hour_filter, + get_global_filter_state, + ) + df = apply_global_month_hour_filter(df, global_filter_data) filter_state = get_global_filter_state(global_filter_data) @@ -202,9 +206,7 @@ def update_psych_chart( start_month, end_month, start_hour, end_hour = 1, 12, 0, 24 # Use local filtering when global filter is not active - df = filter_df_by_month_and_hour( - df, True, [1, 12], [0, 24], [], [], df.columns - ) + df = filter_df_by_month_and_hour(df, True, [1, 12], [0, 24], [], [], df.columns) if data_filter: if min_val <= max_val: diff --git a/pages/select.py b/pages/select.py index d2387b91..eda14778 100644 --- a/pages/select.py +++ b/pages/select.py @@ -163,7 +163,7 @@ def submitted_data( "hour_range": [0, 24], "invert_month": [], "invert_hour": [], - "filter_active": False + "filter_active": False, }, ) location_info = get_location_info( @@ -180,7 +180,7 @@ def submitted_data( "hour_range": [0, 24], "invert_month": [], "invert_hour": [], - "filter_active": False + "filter_active": False, }, ) @@ -211,7 +211,7 @@ def submitted_data( "hour_range": [0, 24], "invert_month": [], "invert_hour": [], - "filter_active": False + "filter_active": False, }, ) else: @@ -226,7 +226,7 @@ def submitted_data( "hour_range": [0, 24], "invert_month": [], "invert_hour": [], - "filter_active": False + "filter_active": False, }, ) except (ValueError, IndexError, KeyError) as e: @@ -242,7 +242,7 @@ def submitted_data( "hour_range": [0, 24], "invert_month": [], "invert_hour": [], - "filter_active": False + "filter_active": False, }, ) raise PreventUpdate diff --git a/pages/summary.py b/pages/summary.py index bed97518..3a26a28a 100644 --- a/pages/summary.py +++ b/pages/summary.py @@ -284,7 +284,9 @@ def update_location_info(ts, df, meta, si_ip): ], prevent_initial_call=False, ) -def degree_day_chart(ts, n_clicks, global_filter_data, df, meta, hdd_value, cdd_value, si_ip): +def degree_day_chart( + ts, n_clicks, global_filter_data, df, meta, hdd_value, cdd_value, si_ip +): """Redraw HDD/CDD chart only when Submit is clicked.""" if df is None or meta is None: @@ -296,7 +298,10 @@ def degree_day_chart(ts, n_clicks, global_filter_data, df, meta, hdd_value, cdd_ # Apply global filter if active if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter - df = apply_global_month_hour_filter(df, global_filter_data, Variables.DBT.col_name) + + df = apply_global_month_hour_filter( + df, global_filter_data, Variables.DBT.col_name + ) hdd_setpoint = hdd_value cdd_setpoint = cdd_value diff --git a/pages/sun.py b/pages/sun.py index 539a9101..b01130d4 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -200,11 +200,19 @@ def monthly_and_cloud_chart(_, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter - df = apply_global_month_hour_filter(df, global_filter_data, - [Variables.GLOB_HOR_RAD.col_name, Variables.DIF_HOR_RAD.col_name, Variables.TOT_SKY_COVER.col_name]) + + df = apply_global_month_hour_filter( + df, + global_filter_data, + [ + Variables.GLOB_HOR_RAD.col_name, + Variables.DIF_HOR_RAD.col_name, + Variables.TOT_SKY_COVER.col_name, + ], + ) # Filter out the filtered rows for solar radiation calculations - if '_is_filtered' in df.columns: - df = df[~df['_is_filtered']] + if "_is_filtered" in df.columns: + df = df[~df["_is_filtered"]] # Sun Radiation monthly = monthly_solar(df, si_ip) @@ -253,6 +261,7 @@ def sun_path_chart(_, view, var, global_local, global_filter_data, df, meta, si_ """Update the contents of tab four. Passing in the polar selection and the general info (df, meta).""" if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + # For sun path chart, we need to filter all sun position related columns target_cols = [ Variables.GLOB_HOR_RAD.col_name, @@ -263,7 +272,7 @@ def sun_path_chart(_, view, var, global_local, global_filter_data, df, meta, si_ Variables.ELEVATION.col_name, Variables.DAY.col_name, Variables.MONTH_NAMES.col_name, - Variables.HOUR.col_name + Variables.HOUR.col_name, ] # Add the selected variable if it's not "None" if var != "None": @@ -308,6 +317,7 @@ def daily(_, var, global_local, global_filter_data, df, meta, si_ip): """Update the contents of tab four section two. Passing in the general info (df, meta).""" if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data, var) custom_inputs = generate_custom_inputs(var) @@ -336,6 +346,7 @@ def daily(_, var, global_local, global_filter_data, df, meta, si_ip): def update_heatmap(_, var, global_local, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + df = apply_global_month_hour_filter(df, global_filter_data, var) custom_inputs = generate_custom_inputs(var) diff --git a/pages/t_rh.py b/pages/t_rh.py index 676faccf..74234ed2 100644 --- a/pages/t_rh.py +++ b/pages/t_rh.py @@ -103,9 +103,16 @@ def layout(): def update_yearly_chart(_, global_local, dd_value, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter - target_columns = [Variables.DBT.col_name, Variables.RH.col_name, Variables.ADAPTIVE_CMF_80_LOW.col_name, - Variables.ADAPTIVE_CMF_80_UP.col_name, Variables.ADAPTIVE_CMF_90_LOW.col_name, - Variables.ADAPTIVE_CMF_90_UP.col_name, Variables.ADAPTIVE_CMF_RMT.col_name] + + target_columns = [ + Variables.DBT.col_name, + Variables.RH.col_name, + Variables.ADAPTIVE_CMF_80_LOW.col_name, + Variables.ADAPTIVE_CMF_80_UP.col_name, + Variables.ADAPTIVE_CMF_90_LOW.col_name, + Variables.ADAPTIVE_CMF_90_UP.col_name, + Variables.ADAPTIVE_CMF_RMT.col_name, + ] df = apply_global_month_hour_filter(df, global_filter_data, target_columns) if dd_value == dropdown_names[var_to_plot[0]]: @@ -145,6 +152,7 @@ def update_yearly_chart(_, global_local, dd_value, global_filter_data, df, meta, def update_daily(_, global_local, dd_value, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + target_columns = [Variables.DBT.col_name, Variables.RH.col_name] df = apply_global_month_hour_filter(df, global_filter_data, target_columns) @@ -210,16 +218,22 @@ def update_heatmap(_, global_local, dd_value, global_filter_data, df, meta, si_i """Update heatmap content.""" if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + target_columns = [Variables.DBT.col_name, Variables.RH.col_name] df = apply_global_month_hour_filter(df, global_filter_data, target_columns) - base_columns = [Variables.HOUR.col_name, Variables.UTC_TIME.col_name, Variables.MONTH_NAMES.col_name, Variables.DAY.col_name] - if '_is_filtered' in df.columns: - base_columns.append('_is_filtered') + base_columns = [ + Variables.HOUR.col_name, + Variables.UTC_TIME.col_name, + Variables.MONTH_NAMES.col_name, + Variables.DAY.col_name, + ] + if "_is_filtered" in df.columns: + base_columns.append("_is_filtered") if dd_value == dropdown_names[var_to_plot[0]]: - if f'_{Variables.DBT.col_name}_original' in df.columns: - base_columns.append(f'_{Variables.DBT.col_name}_original') + if f"_{Variables.DBT.col_name}_original" in df.columns: + base_columns.append(f"_{Variables.DBT.col_name}_original") units = generate_units_degree(si_ip) return dcc.Graph( config=generate_chart_name( @@ -233,8 +247,8 @@ def update_heatmap(_, global_local, dd_value, global_filter_data, df, meta, si_i ), ) else: - if f'_{Variables.DBT.col_name}_original' in df.columns: - base_columns.append(f'_{Variables.DBT.col_name}_original') + if f"_{Variables.DBT.col_name}_original" in df.columns: + base_columns.append(f"_{Variables.DBT.col_name}_original") units = generate_units(si_ip) return dcc.Graph( config=generate_chart_name(TabNames.RELATIVE_HUMIDITY_HEATMAP, meta, units), @@ -263,11 +277,12 @@ def update_table(_, dd_value, global_filter_data, df, si_ip): """Update the contents of descriptive statistics table.""" if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter + target_columns = [Variables.DBT.col_name, Variables.RH.col_name] df = apply_global_month_hour_filter(df, global_filter_data, target_columns) # Filter out the filtered rows to avoid empty columns - if '_is_filtered' in df.columns: - df = df[~df['_is_filtered']] + if "_is_filtered" in df.columns: + df = df[~df["_is_filtered"]] return summary_table_tmp_rh_tab( df[ diff --git a/pages/wind.py b/pages/wind.py index 937509c8..c6078988 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -201,8 +201,6 @@ def daily_wind_rose(): ) - - def layout(): """Contents in the fifth tab 'Wind'.""" return dmc.Stack( @@ -245,8 +243,16 @@ def layout(): ) def update_annual_wind_rose(_, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): - from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state - df = apply_global_month_hour_filter(df, global_filter_data, [Variables.WIND_SPEED.col_name, Variables.WIND_DIR.col_name]) + from pages.lib.layout import ( + apply_global_month_hour_filter, + get_global_filter_state, + ) + + df = apply_global_month_hour_filter( + df, + global_filter_data, + [Variables.WIND_SPEED.col_name, Variables.WIND_DIR.col_name], + ) months = [1, 12] hours = [1, 24] @@ -280,7 +286,10 @@ def update_annual_wind_rose(_, global_filter_data, df, meta, si_ip): def update_tab_wind_speed(_, global_local, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter - df = apply_global_month_hour_filter(df, global_filter_data, Variables.WIND_SPEED.col_name) + + df = apply_global_month_hour_filter( + df, global_filter_data, Variables.WIND_SPEED.col_name + ) speed = heatmap(df, Variables.WIND_SPEED.col_name, global_local, si_ip) units = generate_units(si_ip) @@ -306,7 +315,10 @@ def update_tab_wind_speed(_, global_local, global_filter_data, df, meta, si_ip): def update_tab_wind_direction(_, global_local, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import apply_global_month_hour_filter - df = apply_global_month_hour_filter(df, global_filter_data, Variables.WIND_DIR.col_name) + + df = apply_global_month_hour_filter( + df, global_filter_data, Variables.WIND_DIR.col_name + ) direction = heatmap(df, Variables.WIND_DIR.col_name, global_local, si_ip) units = generate_units(si_ip) @@ -316,8 +328,6 @@ def update_tab_wind_direction(_, global_local, global_filter_data, df, meta, si_ ) - - @callback( [ Output(ElementIds.WINTER_WIND_ROSE, "children"), @@ -457,8 +467,16 @@ def seasonal_chart_caption(month_start, month_end, count, n_calm): ) def update_daily_graphs(_, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): - from pages.lib.layout import apply_global_month_hour_filter, get_global_filter_state - df = apply_global_month_hour_filter(df, global_filter_data, [Variables.WIND_SPEED.col_name, Variables.WIND_DIR.col_name]) + from pages.lib.layout import ( + apply_global_month_hour_filter, + get_global_filter_state, + ) + + df = apply_global_month_hour_filter( + df, + global_filter_data, + [Variables.WIND_SPEED.col_name, Variables.WIND_DIR.col_name], + ) months = [1, 12] else: From 414439b895fd107c323db3833d65a176d04973b2 Mon Sep 17 00:00:00 2001 From: yluo0664 Date: Mon, 27 Oct 2025 21:37:49 +1100 Subject: [PATCH 120/163] fix: formatted the code with removing unused parameters. --- pages/lib/charts_data_explorer.py | 1 - pages/lib/template_graphs.py | 2 -- pages/natural_ventilation.py | 1 - pages/sun.py | 2 -- pages/wind.py | 5 ----- 5 files changed, 11 deletions(-) diff --git a/pages/lib/charts_data_explorer.py b/pages/lib/charts_data_explorer.py index c63738b9..de5450dd 100644 --- a/pages/lib/charts_data_explorer.py +++ b/pages/lib/charts_data_explorer.py @@ -99,7 +99,6 @@ def custom_heatmap(df, global_local, var, time_filter_info, data_filter_info, si ) ) - normal_mask = ~filtered_mask normal_z = df[var].copy() normal_z[filtered_mask] = None diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index 5db67b8f..2e560578 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -452,7 +452,6 @@ def heatmap_with_filter( ) ) - normal_mask = ~filtered_mask normal_z = df[var].copy() normal_z[filtered_mask] = None @@ -594,7 +593,6 @@ def heatmap(df, var, global_local, si_ip): ) ) - normal_mask = ~filtered_mask normal_z = df[var].copy() normal_z[filtered_mask] = None diff --git a/pages/natural_ventilation.py b/pages/natural_ventilation.py index e97181ef..bc7bf585 100644 --- a/pages/natural_ventilation.py +++ b/pages/natural_ventilation.py @@ -13,7 +13,6 @@ tight_margins, month_lst, ) -from pages.lib.template_graphs import filter_df_by_month_and_hour from pages.lib.global_variables import Variables, VariableInfo from pages.lib.global_element_ids import ElementIds from pages.lib.global_id_buttons import IdButtons diff --git a/pages/sun.py b/pages/sun.py index b01130d4..67f519f9 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -4,7 +4,6 @@ import dash import dash_mantine_components as dmc -import numpy as np from dash import dcc from dash_extensions.enrich import Output, Input, State, callback @@ -22,7 +21,6 @@ sun_cloud_tab_explore_dropdown_names, dropdown_names, tight_margins, - month_lst, ) from pages.lib.template_graphs import heatmap, barchart, daily_profile from pages.lib.utils import ( diff --git a/pages/wind.py b/pages/wind.py index c6078988..95d75bcc 100644 --- a/pages/wind.py +++ b/pages/wind.py @@ -11,12 +11,9 @@ from pages.lib.global_id_buttons import IdButtons from pages.lib.global_tab_names import TabNames from pages.lib.utils import ( - title_with_tooltip, generate_chart_name, generate_units, - generate_custom_inputs_time, title_with_link, - dropdown, ) @@ -245,7 +242,6 @@ def update_annual_wind_rose(_, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import ( apply_global_month_hour_filter, - get_global_filter_state, ) df = apply_global_month_hour_filter( @@ -469,7 +465,6 @@ def update_daily_graphs(_, global_filter_data, df, meta, si_ip): if global_filter_data and global_filter_data.get("filter_active", False): from pages.lib.layout import ( apply_global_month_hour_filter, - get_global_filter_state, ) df = apply_global_month_hour_filter( From e771549f6f474d36c39a55c7847157e9ee7fba68 Mon Sep 17 00:00:00 2001 From: yluo0664 Date: Tue, 28 Oct 2025 13:04:07 +1100 Subject: [PATCH 121/163] fix: fixed the errors in the documentation. --- docs/contributing/contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index 3bc89210..fe1d9631 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -118,7 +118,7 @@ Strive to minimize redundancy in your code. ### UI Modifications For UI-related changes: * Avoid unnecessary custom CSS. -* [Use DMC (Design Master Components)](https://www.dash-mantine-components.com/) wherever applicable for layout and styling consistency. +* Use [DMC (Dash Mantine Components)](https://www.dash-mantine-components.com/) wherever applicable for layout and styling consistency. * Ensure visual consistency with existing components. From b26891bdeee4820431c384d04160661d0b63c7ea Mon Sep 17 00:00:00 2001 From: Wenshu Lyu Date: Tue, 28 Oct 2025 13:17:11 +1100 Subject: [PATCH 122/163] test: add Playwright end-to-end tests for all pages and delete Cypress test. --- .github/workflows/python.yml | 2 +- Pipfile | 1 + Pipfile.lock | 356 ++- docs/contributing/contributing.md | 4 +- tests/conftest.py | 6 + tests/node/cypress.config.js | 12 - tests/node/cypress/.gitignore | 2 - tests/node/cypress/e2e/spec.cy.js | 128 - tests/node/cypress/fixtures/example.json | 5 - tests/node/cypress/support/commands.js | 25 - tests/node/cypress/support/e2e.js | 20 - tests/node/package-lock.json | 2207 ----------------- tests/node/package.json | 12 - tests/node/test.epw | 1 - tests/python/test_utils.py | 40 - ....AP.161400_TMYx.2004-2018.epw => test.epw} | 0 tests/test_explorer.py | 66 + tests/test_filter.py | 213 ++ tests/test_natural_ventilation.py | 60 + tests/test_outdoor.py | 47 + tests/test_psy-chart.py | 42 + tests/test_select.py | 44 + tests/test_summary.py | 96 + tests/test_sun.py | 62 + tests/test_t_rh.py | 69 + tests/test_wind.py | 55 + tests/utils.py | 37 + 27 files changed, 1069 insertions(+), 2543 deletions(-) create mode 100644 tests/conftest.py delete mode 100644 tests/node/cypress.config.js delete mode 100644 tests/node/cypress/.gitignore delete mode 100644 tests/node/cypress/e2e/spec.cy.js delete mode 100644 tests/node/cypress/fixtures/example.json delete mode 100644 tests/node/cypress/support/commands.js delete mode 100644 tests/node/cypress/support/e2e.js delete mode 100644 tests/node/package-lock.json delete mode 100644 tests/node/package.json delete mode 120000 tests/node/test.epw delete mode 100644 tests/python/test_utils.py rename tests/{python/ITA_ER_Bologna-Marconi.AP.161400_TMYx.2004-2018.epw => test.epw} (100%) create mode 100644 tests/test_explorer.py create mode 100644 tests/test_filter.py create mode 100644 tests/test_natural_ventilation.py create mode 100644 tests/test_outdoor.py create mode 100644 tests/test_psy-chart.py create mode 100644 tests/test_select.py create mode 100644 tests/test_summary.py create mode 100644 tests/test_sun.py create mode 100644 tests/test_t_rh.py create mode 100644 tests/test_wind.py create mode 100644 tests/utils.py diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 32f7e7c3..624d8fdb 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -33,4 +33,4 @@ jobs: - name: Test Clima run: |- - pipenv run python -m pytest + pipenv run pytest --base-url=http://127.0.0.1:8080 diff --git a/Pipfile b/Pipfile index 0c01f3de..47a42cee 100644 --- a/Pipfile +++ b/Pipfile @@ -16,6 +16,7 @@ pandas = "==2.2.0" numpy = "==1.26.3" dash-iconify = "*" scipy = "==1.12.0" +pytest-playwright = "*" [dev-packages] cleanpy = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 8e475b6d..36d2cf14 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "4f09fe1d5fd82b15503fd3b0c1606e5e6139df983488fdc78c72555daf01e167" + "sha256": "9f0d0ca6f0508936380679fc9cc581c615766dfc11d9eced4868e9d324cee11e" }, "pipfile-spec": 6, "requires": { @@ -34,96 +34,130 @@ }, "certifi": { "hashes": [ - "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", - "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5" + "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de", + "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43" ], "markers": "python_version >= '3.7'", - "version": "==2025.8.3" + "version": "==2025.10.5" }, "charset-normalizer": { "hashes": [ - "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", - "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0", - "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", - "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601", - "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", - "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", - "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c", - "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", - "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", - "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", - "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432", - "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", - "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", - "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", - "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", - "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19", - "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", - "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e", - "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4", - "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7", - "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312", - "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", - "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", - "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", - "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", - "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99", - "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b", - "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", - "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", - "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", - "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", - "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", - "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0", - "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc", - "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", - "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", - "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a", - "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40", - "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", - "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", - "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", - "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", - "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05", - "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", - "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c", - "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a", - "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", - "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34", - "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9", - "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", - "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", - "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", - "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b", - "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", - "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942", - "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", - "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", - "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b", - "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", - "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669", - "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0", - "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", - "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", - "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe", - "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", - "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", - "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", - "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2", - "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca", - "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", - "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f", - "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb", - "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", - "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557", - "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", - "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7", - "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72", - "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", - "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9" + "sha256:027f6de494925c0ab2a55eab46ae5129951638a49a34d87f4c3eda90f696b4ad", + "sha256:077fbb858e903c73f6c9db43374fd213b0b6a778106bc7032446a8e8b5b38b93", + "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", + "sha256:0d3d8f15c07f86e9ff82319b3d9ef6f4bf907608f53fe9d92b28ea9ae3d1fd89", + "sha256:0f04b14ffe5fdc8c4933862d8306109a2c51e0704acfa35d51598eb45a1e89fc", + "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", + "sha256:194f08cbb32dc406d6e1aea671a68be0823673db2832b38405deba2fb0d88f63", + "sha256:1bee1e43c28aa63cb16e5c14e582580546b08e535299b8b6158a7c9c768a1f3d", + "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", + "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", + "sha256:244bfb999c71b35de57821b8ea746b24e863398194a4014e4c76adc2bbdfeff0", + "sha256:2677acec1a2f8ef614c6888b5b4ae4060cc184174a938ed4e8ef690e15d3e505", + "sha256:277e970e750505ed74c832b4bf75dac7476262ee2a013f5574dd49075879e161", + "sha256:2aaba3b0819274cc41757a1da876f810a3e4d7b6eb25699253a4effef9e8e4af", + "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", + "sha256:2c9d3c380143a1fedbff95a312aa798578371eb29da42106a29019368a475318", + "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", + "sha256:31fd66405eaf47bb62e8cd575dc621c56c668f27d46a61d975a249930dd5e2a4", + "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", + "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", + "sha256:44c2a8734b333e0578090c4cd6b16f275e07aa6614ca8715e6c038e865e70576", + "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", + "sha256:4902828217069c3c5c71094537a8e623f5d097858ac6ca8252f7b4d10b7560f1", + "sha256:4bd5d4137d500351a30687c2d3971758aac9a19208fc110ccb9d7188fbe709e8", + "sha256:4fe7859a4e3e8457458e2ff592f15ccb02f3da787fcd31e0183879c3ad4692a1", + "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", + "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", + "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", + "sha256:5947809c8a2417be3267efc979c47d76a079758166f7d43ef5ae8e9f92751f88", + "sha256:5ae497466c7901d54b639cf42d5b8c1b6a4fead55215500d2f486d34db48d016", + "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", + "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", + "sha256:5cb4d72eea50c8868f5288b7f7f33ed276118325c1dfd3957089f6b519e1382a", + "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", + "sha256:5f819d5fe9234f9f82d75bdfa9aef3a3d72c4d24a6e57aeaebba32a704553aa0", + "sha256:64b55f9dce520635f018f907ff1b0df1fdc31f2795a922fb49dd14fbcdf48c84", + "sha256:6515f3182dbe4ea06ced2d9e8666d97b46ef4c75e326b79bb624110f122551db", + "sha256:65e2befcd84bc6f37095f5961e68a6f077bf44946771354a28ad434c2cce0ae1", + "sha256:6aee717dcfead04c6eb1ce3bd29ac1e22663cdea57f943c87d1eab9a025438d7", + "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", + "sha256:6e1fcf0720908f200cd21aa4e6750a48ff6ce4afe7ff5a79a90d5ed8a08296f8", + "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", + "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", + "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", + "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", + "sha256:778d2e08eda00f4256d7f672ca9fef386071c9202f5e4607920b86d7803387f2", + "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", + "sha256:798d75d81754988d2565bff1b97ba5a44411867c0cf32b77a7e8f8d84796b10d", + "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", + "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", + "sha256:7c308f7e26e4363d79df40ca5b2be1c6ba9f02bdbccfed5abddb7859a6ce72cf", + "sha256:7fa17817dc5625de8a027cb8b26d9fefa3ea28c8253929b8d6649e705d2835b6", + "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", + "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", + "sha256:837c2ce8c5a65a2035be9b3569c684358dfbf109fd3b6969630a87535495ceaa", + "sha256:840c25fb618a231545cbab0564a799f101b63b9901f2569faecd6b222ac72381", + "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", + "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", + "sha256:8ef3c867360f88ac904fd3f5e1f902f13307af9052646963ee08ff4f131adafc", + "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", + "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", + "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", + "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", + "sha256:9cd98cdc06614a2f768d2b7286d66805f94c48cde050acdbbb7db2600ab3197e", + "sha256:9d1bb833febdff5c8927f922386db610b49db6e0d4f4ee29601d71e7c2694313", + "sha256:9f7fcd74d410a36883701fafa2482a6af2ff5ba96b9a620e9e0721e28ead5569", + "sha256:a59cb51917aa591b1c4e6a43c132f0cdc3c76dbad6155df4e28ee626cc77a0a3", + "sha256:a61900df84c667873b292c3de315a786dd8dac506704dea57bc957bd31e22c7d", + "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", + "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", + "sha256:a8bf8d0f749c5757af2142fe7903a9df1d2e8aa3841559b2bad34b08d0e2bcf3", + "sha256:a9768c477b9d7bd54bc0c86dbaebdec6f03306675526c9927c0e8a04e8f94af9", + "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", + "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", + "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", + "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", + "sha256:b5d84d37db046c5ca74ee7bb47dd6cbc13f80665fdde3e8040bdd3fb015ecb50", + "sha256:b7cf1017d601aa35e6bb650b6ad28652c9cd78ee6caff19f3c28d03e1c80acbf", + "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", + "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", + "sha256:c4ef880e27901b6cc782f1b95f82da9313c0eb95c3af699103088fa0ac3ce9ac", + "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", + "sha256:ca5862d5b3928c4940729dacc329aa9102900382fea192fc5e52eb69d6093815", + "sha256:cb01158d8b88ee68f15949894ccc6712278243d95f344770fa7593fa2d94410c", + "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", + "sha256:cc00f04ed596e9dc0da42ed17ac5e596c6ccba999ba6bd92b0e0aef2f170f2d6", + "sha256:cd09d08005f958f370f539f186d10aec3377d55b9eeb0d796025d4886119d76e", + "sha256:cd4b7ca9984e5e7985c12bc60a6f173f3c958eae74f3ef6624bb6b26e2abbae4", + "sha256:ce8a0633f41a967713a59c4139d29110c07e826d131a316b50ce11b1d79b4f84", + "sha256:cead0978fc57397645f12578bfd2d5ea9138ea0fac82b2f63f7f7c6877986a69", + "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", + "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", + "sha256:d9c7f57c3d666a53421049053eaacdd14bbd0a528e2186fcb2e672effd053bb0", + "sha256:d9e45d7faa48ee908174d8fe84854479ef838fc6a705c9315372eacbc2f02897", + "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", + "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", + "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", + "sha256:e824f1492727fa856dd6eda4f7cee25f8518a12f3c4a56a74e8095695089cf6d", + "sha256:e912091979546adf63357d7e2ccff9b44f026c075aeaf25a52d0e95ad2281074", + "sha256:eaabd426fe94daf8fd157c32e571c85cb12e66692f15516a83a03264b08d06c3", + "sha256:ebf3e58c7ec8a8bed6d66a75d7fb37b55e5015b03ceae72a8e7c74495551e224", + "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", + "sha256:eecbc200c7fd5ddb9a7f16c7decb07b566c29fa2161a16cf67b8d068bd21690a", + "sha256:f155a433c2ec037d4e8df17d18922c3a0d9b3232a396690f17175d2946f0218d", + "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", + "sha256:f34be2938726fc13801220747472850852fe6b1ea75869a048d6f896838c896f", + "sha256:f820802628d2694cb7e56db99213f930856014862f3fd943d290ea8438d07ca8", + "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", + "sha256:f8e160feb2aed042cd657a72acc0b481212ed28b1b9a95c0cee1621b524e1966", + "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", + "sha256:fa09f53c465e532f4d3db095e0c55b615f010ad81803d383195b6b5ca6cbf5f3", + "sha256:faa3a41b2b66b6e50f84ae4a68c64fcd0c44355741c6374813a800cd6695db9e", + "sha256:fd44c878ea55ba351104cb93cc85e74916eb8fa440ca7903e57575e97394f608" ], "markers": "python_version >= '3.7'", - "version": "==3.4.3" + "version": "==3.4.4" }, "click": { "hashes": [ @@ -207,6 +241,66 @@ "markers": "python_version >= '3.7'", "version": "==2.0.2" }, + "greenlet": { + "hashes": [ + "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b", + "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735", + "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079", + "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d", + "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433", + "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58", + "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52", + "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31", + "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246", + "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f", + "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671", + "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8", + "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d", + "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f", + "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0", + "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd", + "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337", + "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0", + "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633", + "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b", + "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa", + "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31", + "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9", + "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b", + "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4", + "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc", + "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c", + "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98", + "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f", + "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c", + "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590", + "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3", + "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2", + "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9", + "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5", + "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02", + "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0", + "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1", + "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c", + "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594", + "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5", + "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d", + "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a", + "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6", + "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b", + "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df", + "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945", + "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae", + "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb", + "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504", + "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb", + "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01", + "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c", + "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968" + ], + "markers": "python_version >= '3.9'", + "version": "==3.2.4" + }, "h5py": { "hashes": [ "sha256:016e89d3be4c44f8d5e115fab60548e518ecd9efe9fa5c5324505a90773e6f03", @@ -241,11 +335,11 @@ }, "idna": { "hashes": [ - "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", - "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3" + "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", + "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902" ], - "markers": "python_version >= '3.6'", - "version": "==3.10" + "markers": "python_version >= '3.8'", + "version": "==3.11" }, "importlib-metadata": { "hashes": [ @@ -255,6 +349,14 @@ "markers": "python_version >= '3.9'", "version": "==8.7.0" }, + "iniconfig": { + "hashes": [ + "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", + "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12" + ], + "markers": "python_version >= '3.10'", + "version": "==2.3.0" + }, "itsdangerous": { "hashes": [ "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", @@ -502,6 +604,20 @@ "markers": "python_version >= '3.9'", "version": "==2.2.0" }, + "playwright": { + "hashes": [ + "sha256:012dc89ccdcbd774cdde8aeee14c08e0dd52ddb9135bf10e9db040527386bd76", + "sha256:25b0d6b3fd991c315cca33c802cf617d52980108ab8431e3e1d37b5de755c10e", + "sha256:29a0777c4ce1273acf90c87e4ae2fe0130182100d99bcd2ae5bf486093044838", + "sha256:29e6d1558ad9d5b5c19cbec0a72f6a2e35e6353cd9f262e22148685b86759f90", + "sha256:7eb5956473ca1951abb51537e6a0da55257bb2e25fc37c2b75af094a5c93736c", + "sha256:8290cf27a5d542e2682ac274da423941f879d07b001f6575a5a3a257b1d4ba1c", + "sha256:c6d4d8f6f8c66c483b0835569c7f0caa03230820af8e500c181c93509c92d831", + "sha256:d7da108a95001e412effca4f7610de79da1637ccdf670b1ae3fdc08b9694c034" + ], + "markers": "python_version >= '3.9'", + "version": "==1.55.0" + }, "plotly": { "hashes": [ "sha256:23aa8ea2f4fb364a20d34ad38235524bd9d691bf5299e800bca608c31e8db8de", @@ -511,6 +627,14 @@ "markers": "python_version >= '3.6'", "version": "==5.18.0" }, + "pluggy": { + "hashes": [ + "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", + "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746" + ], + "markers": "python_version >= '3.9'", + "version": "==1.6.0" + }, "pvlib": { "hashes": [ "sha256:ead96f47898befd7728ab0b61b9747231008e151ef78a26d5e41d0b6a95a3a9d", @@ -520,6 +644,47 @@ "markers": "python_version >= '3.6'", "version": "==0.9.1" }, + "pyee": { + "hashes": [ + "sha256:48195a3cddb3b1515ce0695ed76036b5ccc2ef3a9f963ff9f77aec0139845498", + "sha256:b391e3c5a434d1f5118a25615001dbc8f669cf410ab67d04c4d4e07c55481c37" + ], + "markers": "python_version >= '3.8'", + "version": "==13.0.0" + }, + "pygments": { + "hashes": [ + "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", + "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b" + ], + "markers": "python_version >= '3.8'", + "version": "==2.19.2" + }, + "pytest": { + "hashes": [ + "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", + "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79" + ], + "markers": "python_version >= '3.9'", + "version": "==8.4.2" + }, + "pytest-base-url": { + "hashes": [ + "sha256:02748589a54f9e63fcbe62301d6b0496da0d10231b753e950c63e03aee745d45", + "sha256:3ad15611778764d451927b2a53240c1a7a591b521ea44cebfe45849d2d2812e6" + ], + "markers": "python_version >= '3.8'", + "version": "==2.1.0" + }, + "pytest-playwright": { + "hashes": [ + "sha256:94b551b2677ecdc16284fcd6a4f0045eafda47a60e74410f3fe4d8260e12cabf", + "sha256:fcc46510fb75f8eba6df3bc8e84e4e902483d92be98075f20b9d160651a36d90" + ], + "index": "pypi", + "markers": "python_version >= '3.9'", + "version": "==0.7.1" + }, "pythermalcomfort": { "hashes": [ "sha256:607995f6920a03911c7b9fddd06d819db1fa6e658d742b4ec8395a2c90707da5", @@ -537,6 +702,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9.0.post0" }, + "python-slugify": { + "hashes": [ + "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", + "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856" + ], + "markers": "python_version >= '3.7'", + "version": "==8.0.4" + }, "pytz": { "hashes": [ "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", @@ -617,6 +790,13 @@ "markers": "python_version >= '3.9'", "version": "==9.1.2" }, + "text-unidecode": { + "hashes": [ + "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", + "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" + ], + "version": "==1.3" + }, "typing-extensions": { "hashes": [ "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", diff --git a/docs/contributing/contributing.md b/docs/contributing/contributing.md index fe1d9631..2aa50ceb 100644 --- a/docs/contributing/contributing.md +++ b/docs/contributing/contributing.md @@ -135,9 +135,9 @@ pipenv sync From the root directory, run: ```bash -cd tests/node +cd tests -npx cypress run +pipenv run pytest --base-url=http://127.0.0.1:8080 ``` ## Submitting changes diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..d17fb7bb --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,6 @@ +import pytest + + +@pytest.fixture(scope="session") +def base_url(pytestconfig): + return pytestconfig.getoption("base_url") diff --git a/tests/node/cypress.config.js b/tests/node/cypress.config.js deleted file mode 100644 index dae5f35d..00000000 --- a/tests/node/cypress.config.js +++ /dev/null @@ -1,12 +0,0 @@ -const { defineConfig } = require("cypress"); - -module.exports = defineConfig({ - defaultCommandTimeout: 30 * 1000, - video: true, - videoCompression: true, - e2e: { - setupNodeEvents(on, config) { - // implement node event listeners here - } - }, -}); diff --git a/tests/node/cypress/.gitignore b/tests/node/cypress/.gitignore deleted file mode 100644 index 2ca81ab1..00000000 --- a/tests/node/cypress/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -screenshots -videos \ No newline at end of file diff --git a/tests/node/cypress/e2e/spec.cy.js b/tests/node/cypress/e2e/spec.cy.js deleted file mode 100644 index f0ea1ae6..00000000 --- a/tests/node/cypress/e2e/spec.cy.js +++ /dev/null @@ -1,128 +0,0 @@ -/* - ⚠️ 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) { - // 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() { - cy.get('input[type=file]').selectFile('test.epw', { force: true }); -} - -describe('Clima', () => { - it('loads all tabs for uploaded EPW', () => { - 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!'); - cy.contains('Current Location: Bologna Marconi AP, ITA'); - - // Climate Summary - click_tab('Climate Summary'); - cy.contains('data collected between 2004 and 2018'); - cy.contains('Longitude: 11.2969'); - 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('Average yearly temperature: 14.5 °C'); - cy.contains('Hottest yearly temperature (99%): 34.0 °C'); - cy.contains('Coldest yearly temperature (1%): -2.0 °C'); - cy.contains('Annual cumulative horizontal solar radiation: 1546.12 kWh/m2'); - cy.contains('Percentage of diffuse horizontal solar radiation: 39.4 %'); - - // Temperature and Humidity - click_tab('Temperature and Humidity'); - cy.contains('Yearly Chart'); - cy.contains('Dry bulb temperature (°C)'); - // TODO: simulate mouseover - cy.contains('Daily chart'); - // TODO: simulate mouseover - cy.contains('Heatmap chart'); - // TODO: simulate mouseover - cy.contains('Descriptive statistics'); - cy.contains('12.1'); // January max - - // Sun and Clouds - click_tab('Sun and Clouds'); - cy.contains('Sun path chart'); - // TODO - cy.contains('Global and Diffuse Horizontal Solar Radiation (Wh/m²)'); - // TODO - cy.contains('Cloud coverage'); - // TODO - cy.contains('Daily charts'); - // TODO - - // Wind - click_tab('Wind'); - cy.contains('Annual Wind Rose'); - // TODO - cy.contains('Seasonal Wind Rose'); - cy.contains('Observations between the months of Dec and Feb between 01:00 hours and 24:00 hours.'); - cy.contains('Selected observations 2160 of 8760, or 24 %.'); - cy.contains('40 observations have calm winds.'); - // TODO - cy.contains('Daily Wind Rose'); - // TODO - - // Psychrometric Chart - click_tab('Psychrometric Chart'); - // TODO - // cy.contains('Humidity Ratio g water/kg dry air'); - - // Natural Ventilation - click_tab('Natural Ventilation'); - // TODO - cy.contains('Outdoor dry-bulb air temperature range'); - cy.contains('Hours when the Dry bulb temperature is in the range 10 to 24 °C'); - cy.contains('Percentage of hours the Dry bulb temperature is in the range 10 to 24 °C'); - - // Outdoor Comfort - click_tab('Outdoor Comfort'); - // TODO - cy.contains('The Best Weather Condition is: UTCI No Sun No Wind Categories'); - // TODO - cy.contains('UTCI thermal stress chart'); - cy.contains('no thermal stress'); - cy.contains('UTCI thermal stress distribution'); - - // Data Explorer - click_tab('Data Explorer'); - // TODO - }); - - it('responds to banner radio buttons', () => { - cy.visit('http://127.0.0.1:8080'); - load_epw() - cy.contains('The EPW was successfully loaded!'); - click_tab('Temperature and Humidity') - // Expand the "Data Display Options" nav section to access controls - cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true }); - cy.contains('Global', { timeout: 10000 }).click({ force: true }); - cy.contains('-40'); // Global minimum: not something you see in Italy! - cy.get('#nav-group-controls', { timeout: 10000 }).should('exist').click({ force: true }); - cy.contains('IP').click({ force: true }); - cy.contains('100'); // Not a Celsius temperature! - cy.contains('Dry bulb temperature (°F)'); - }); -}) \ No newline at end of file diff --git a/tests/node/cypress/fixtures/example.json b/tests/node/cypress/fixtures/example.json deleted file mode 100644 index 02e42543..00000000 --- a/tests/node/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/tests/node/cypress/support/commands.js b/tests/node/cypress/support/commands.js deleted file mode 100644 index 66ea16ef..00000000 --- a/tests/node/cypress/support/commands.js +++ /dev/null @@ -1,25 +0,0 @@ -// *********************************************** -// This example commands.js shows you how to -// create various custom commands and overwrite -// existing commands. -// -// For more comprehensive examples of custom -// commands please read more here: -// https://on.cypress.io/custom-commands -// *********************************************** -// -// -// -- This is a parent command -- -// Cypress.Commands.add('login', (email, password) => { ... }) -// -// -// -- This is a child command -- -// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) -// -// -// -- This is a dual command -- -// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) -// -// -// -- This will overwrite an existing command -- -// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) \ No newline at end of file diff --git a/tests/node/cypress/support/e2e.js b/tests/node/cypress/support/e2e.js deleted file mode 100644 index 0e7290a1..00000000 --- a/tests/node/cypress/support/e2e.js +++ /dev/null @@ -1,20 +0,0 @@ -// *********************************************************** -// This example support/e2e.js is processed and -// loaded automatically before your test files. -// -// This is a great place to put global configuration and -// behavior that modifies Cypress. -// -// You can change the location of this file or turn off -// automatically serving support files with the -// 'supportFile' configuration option. -// -// You can read more here: -// https://on.cypress.io/configuration -// *********************************************************** - -// Import commands.js using ES2015 syntax: -import './commands' - -// Alternatively you can use CommonJS syntax: -// require('./commands') \ No newline at end of file diff --git a/tests/node/package-lock.json b/tests/node/package-lock.json deleted file mode 100644 index 37f5fdc1..00000000 --- a/tests/node/package-lock.json +++ /dev/null @@ -1,2207 +0,0 @@ -{ - "name": "clima", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "clima", - "version": "0.0.0", - "devDependencies": { - "cypress": "^13.8.1" - } - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@cypress/request": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.9.tgz", - "integrity": "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~4.0.4", - "http-signature": "~1.4.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.14.0", - "safe-buffer": "^5.1.2", - "tough-cookie": "^5.0.0", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, - "dependencies": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" - } - }, - "node_modules/@cypress/xvfb/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/@types/node": { - "version": "18.18.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.10.tgz", - "integrity": "sha512-luANqZxPmjTll8bduz4ACs/lNTCLuWssCyjqTY9yLdsv1xnViQp3ISKwsEWOIecO13JWUqjVdig/Vjjc09o8uA==", - "dev": true, - "optional": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/sinonjs__fake-timers": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.1.tgz", - "integrity": "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g==", - "dev": true - }, - "node_modules/@types/sizzle": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.6.tgz", - "integrity": "sha512-m04Om5Gz6kbjUwAQ7XJJQ30OdEFsSmAVsvn4NYwcTRyMVpKKa1aPuESw1n2CxS5fYkOQv3nHgDKeNa8e76fUkw==", - "dev": true - }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "dev": true, - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/at-least-node": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", - "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/check-more-types": { - "version": "2.24.0", - "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", - "integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-table3": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz", - "integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", - "dev": true, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/common-tags": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", - "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cypress": { - "version": "13.8.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.8.1.tgz", - "integrity": "sha512-Uk6ovhRbTg6FmXjeZW/TkbRM07KPtvM5gah1BIMp4Y2s+i/NMxgaLw0+PbYTOdw1+egE0FP3mWRiGcRkjjmhzA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@cypress/request": "^3.0.0", - "@cypress/xvfb": "^1.2.4", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.7.1", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-ci": "^3.0.1", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.1", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "bin": { - "cypress": "bin/cypress" - }, - "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" - } - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/dayjs": { - "version": "1.11.10", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", - "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "dependencies": { - "pify": "^2.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT" - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "dev": true, - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "dependencies": { - "async": "^3.2.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "dev": true, - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "dev": true, - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "dev": true, - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true, - "license": "(AFL-2.1 OR BSD-3-Clause)" - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true - }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonfile/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/jsprim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", - "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "node_modules/lazy-ass": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", - "integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==", - "dev": true, - "engines": { - "node": "> 0.8" - } - }, - "node_modules/listr2": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", - "integrity": "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g==", - "dev": true, - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.1", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "dev": true - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/log-update/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "dev": true - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "node_modules/pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, - "dependencies": { - "throttleit": "^1.0.0" - } - }, - "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sshpk": { - "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/throttleit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", - "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/tldts": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", - "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^6.1.86" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.86", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", - "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "dependencies": { - "rimraf": "^3.0.0" - }, - "engines": { - "node": ">=8.17.0" - } - }, - "node_modules/tough-cookie": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", - "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true, - "license": "Unlicense" - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true, - "optional": true - }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "license": "MIT", - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "dev": true, - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - } - } -} diff --git a/tests/node/package.json b/tests/node/package.json deleted file mode 100644 index cb40d108..00000000 --- a/tests/node/package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "clima", - "version": "0.0.0", - "description": "Tests for Clima", - "scripts": { - "cy:run": "cypress run", - "cy:open": "cypress open" - }, - "devDependencies": { - "cypress": "^13.8.1" - } -} diff --git a/tests/node/test.epw b/tests/node/test.epw deleted file mode 120000 index d4d1daed..00000000 --- a/tests/node/test.epw +++ /dev/null @@ -1 +0,0 @@ -../python/ITA_ER_Bologna-Marconi.AP.161400_TMYx.2004-2018.epw \ No newline at end of file diff --git a/tests/python/test_utils.py b/tests/python/test_utils.py deleted file mode 100644 index ab085c42..00000000 --- a/tests/python/test_utils.py +++ /dev/null @@ -1,40 +0,0 @@ -import os - -import pandas as pd - -from config import UnitSystem - -import requests - -from pages.lib.utils import summary_table_tmp_rh_tab -from pages.lib.extract_df import get_data, create_df - - -def save_epw_test(path_file): - test_url = "http://climate.onebuilding.org/WMO_Region_6_Europe/ITA_Italy/ER_Emilia-Romagna/ITA_ER_Bologna-Marconi.AP.161400_TMYx.2004-2018.zip" - - lines = get_data(source_url=test_url) - df, _ = create_df(lst=lines, file_name=test_url) - - df.to_pickle(path_file, compression="gzip") - - -def import_epw_test(): - epw_test_file_path = "epw_test.pkl" - - if not os.path.isfile(epw_test_file_path): - save_epw_test(path_file=epw_test_file_path) - - return pd.read_pickle(epw_test_file_path, compression="gzip") - - -def test_summary_table_tmp_rh_tab(): - try: - # check tha the climate.onebuilding website is on - print(requests.get("https://climate.onebuilding.org", timeout=2)) - df = import_epw_test() - data_table = summary_table_tmp_rh_tab(df, "RH", UnitSystem.SI) - - assert data_table.data[0]["month"] == "Jan" - except requests.exceptions.ConnectionError: - pass diff --git a/tests/python/ITA_ER_Bologna-Marconi.AP.161400_TMYx.2004-2018.epw b/tests/test.epw similarity index 100% rename from tests/python/ITA_ER_Bologna-Marconi.AP.161400_TMYx.2004-2018.epw rename to tests/test.epw diff --git a/tests/test_explorer.py b/tests/test_explorer.py new file mode 100644 index 00000000..115d2a37 --- /dev/null +++ b/tests/test_explorer.py @@ -0,0 +1,66 @@ +import pytest +from playwright.sync_api import Page, expect +from utils import upload_epw_file, open_tab + + +@pytest.fixture(scope="function", autouse=True) +def setup(page: Page, base_url): + """Setup: Go to base URL, upload EPW file, and open Explorer page""" + page.goto(base_url) + upload_epw_file(page) + open_tab(page, "Data Explorer") + yield + + +# -------------------- Test Title & Section Headings -------------------- +def test_explorer_titles(page: Page): + """Check key titles and headings are visible""" + expect(page.get_by_text("Select a variable:")).to_be_visible() + expect(page.get_by_text("Yearly chart")).to_be_visible() + expect(page.get_by_text("Daily chart")).to_be_visible() + expect(page.get_by_text("Heatmap chart")).to_be_visible() + expect(page.get_by_text("Descriptive statistics")).to_be_visible() + expect(page.get_by_text("Customizable heatmap")).to_be_visible() + expect(page.get_by_text("More charts")).to_be_visible() + + +# -------------------- Test Section One Charts -------------------- +def test_section_one_charts(page: Page): + """Check if Section One charts render properly""" + expect(page.locator("#yearly-explore")).to_be_visible() + expect(page.locator("#query-daily")).to_be_visible() + expect(page.locator("#query-heatmap")).to_be_visible() + expect(page.locator("#table-data-explorer")).to_be_visible() + + +# -------------------- Test Section Two Charts -------------------- +def test_section_two_charts(page: Page): + """Ensure Section Two charts and summary elements appear""" + expect(page.locator("#custom-heatmap")).to_be_visible() + + +# -------------------- Test Section Three Controls -------------------- +def test_section_three_controls(page: Page): + """Check 'More charts' section inputs and controls""" + expect(page.locator("#explorer-sec3-var-x-dropdown")).to_be_visible() + expect(page.locator("#explorer-sec3-var-y-dropdown")).to_be_visible() + expect(page.locator("#explorer-sec3-colorby-dropdown")).to_be_visible() + + +# -------------------- Test Section Three Charts -------------------- +def test_section_three_charts(page: Page): + """Check if 2-variable and 3-variable graphs are rendered""" + expect(page.locator("#three-var")).to_be_visible() + expect(page.locator("#two-var")).to_be_visible() + + +# -------------------- Test Dropdown Interaction -------------------- +def test_explorer_dropdown_interaction(page: Page): + """Switch variable in dropdown and verify chart reloads""" + dropdown = page.locator("#sec1-var-dropdown") + dropdown.click() + options = page.locator(".VirtualizedSelectOption") + assert options.count() > 0 + options.filter(has_text="Relative humidity").first + expect(page.locator("#yearly-explore")).to_be_visible() + expect(page.locator("#query-heatmap")).to_be_visible() diff --git a/tests/test_filter.py b/tests/test_filter.py new file mode 100644 index 00000000..7347e93e --- /dev/null +++ b/tests/test_filter.py @@ -0,0 +1,213 @@ +import re +from typing import List, Tuple, Optional + +import pytest +from playwright.sync_api import Page, expect, Locator + +from utils import upload_epw_file + + +def ensure_local_mode_and_invert_off(page: Page): + """Ensure 'Local' mode is active and 'Invert' is off.""" + try: + page.get_by_text("Local", exact=True).click() + except Exception: + pass + try: + invert = ( + page.get_by_text("Invert", exact=True) + .locator("..") + .locator("input[type=checkbox]") + ) + if invert.is_checked(): + invert.uncheck() + except Exception: + pass + + +def open_tools_menu_and_filter_section(page: Page): + """Open Tools Menu → Filter function, using the simplest reliable click.""" + header = page.get_by_text("Tools Menu", exact=False).first + header.scroll_into_view_if_needed() + header.click() + + try: + page.get_by_text("Filter function", exact=False).first.click() + except Exception: + pass + + expect( + page.get_by_text("Apply month and hour filter", exact=False).first + ).to_be_visible() + ensure_local_mode_and_invert_off(page) + + +BASELINE_MONTH: Tuple[int, int] = (1, 12) +BASELINE_HOUR: Tuple[int, int] = (0, 24) +NARROW_MONTHS: List[Tuple[int, int]] = [(1, 4), (8, 12)] +NARROW_HOURS: List[Tuple[int, int]] = [(0, 3), (18, 24)] + + +def _sliders_in_group(page: Page, group_label: str) -> List[Locator]: + """Return the two sliders for a given group using global order: [month_lo, month_hi, hour_lo, hour_hi].""" + sliders = page.get_by_role("slider") + n = sliders.count() + if n >= 4: + if "month" in group_label.lower(): + return [sliders.nth(0), sliders.nth(1)] + if "hour" in group_label.lower(): + return [sliders.nth(2), sliders.nth(3)] + return [sliders.nth(0), sliders.nth(1)] if n >= 2 else [] + + +def _read_slider_value(slider: Locator) -> Optional[float]: + """Read a slider's numeric value.""" + for attr in ("aria-valuenow", "value"): + v = slider.get_attribute(attr) + if v: + try: + return float(v) + except ValueError: + continue + return None + + +def _keyboard_move_to( + slider: Locator, target: float, vmin: float, vmax: float, step: float = 1.0 +): + """Move a slider thumb to target using only keyboard arrows.""" + slider.scroll_into_view_if_needed() + slider.focus() + current = _read_slider_value(slider) + if current is None: + slider.press("Home") + current = vmin + diff = target - current + key = "ArrowRight" if diff > 0 else "ArrowLeft" + steps = int(abs(diff) / max(step, 1)) + steps = min(steps, 200) + for _ in range(steps): + slider.press(key) + + +def _set_range( + page: Page, + group_label: str, + target_lo: float, + target_hi: float, + domain_lo: float, + domain_hi: float, + step: float, +): + sliders = _sliders_in_group(page, group_label) + if len(sliders) < 2: + return + lo, hi = sliders[0], sliders[1] + _keyboard_move_to(lo, target_lo, domain_lo, domain_hi, step) + _keyboard_move_to(hi, target_hi, domain_lo, domain_hi, step) + + +def set_month_range(page: Page, m_start: int, m_end: int): + _set_range(page, "Month Range", m_start, m_end, 1, 12, step=1.0) + + +def set_hour_range(page: Page, h_start: int, h_end: int): + _set_range(page, "Hour Range", h_start, h_end, 0, 24, step=1.0) + + +def _click_apply(page: Page): + """Click the 'Apply' button.""" + try: + page.get_by_role( + "button", name=re.compile("Apply month and hour filter", re.I) + ).first.click() + except Exception: + page.get_by_text("Apply month and hour filter", exact=False).first.click() + + +def apply_filter(page: Page, month_range: Tuple[int, int], hour_range: Tuple[int, int]): + """Apply the selected filter settings.""" + set_month_range(page, month_range[0], month_range[1]) + set_hour_range(page, hour_range[0], hour_range[1]) + _click_apply(page) + + +def _chart_state_hash(page: Page, chart_selector: str) -> str: + """Generate a simple hash from chart inner HTML.""" + node = page.locator(chart_selector).first + if not node.is_visible(): + node.scroll_into_view_if_needed() + html = node.inner_html() + return str(hash(html)) + + +def assert_chart_changes_by_three_steps(page: Page, chart_selector: str): + """Test chart reactivity across month/hour/both filter changes without screenshots.""" + base_hash = _chart_state_hash(page, chart_selector) + + changed = False + for months in NARROW_MONTHS: + apply_filter(page, months, BASELINE_HOUR) + if _chart_state_hash(page, chart_selector) != base_hash: + changed = True + break + + if not changed: + for hours in NARROW_HOURS: + apply_filter(page, BASELINE_MONTH, hours) + if _chart_state_hash(page, chart_selector) != base_hash: + changed = True + break + + if not changed: + months, hours = NARROW_MONTHS[0], NARROW_HOURS[0] + apply_filter(page, months, hours) + if _chart_state_hash(page, chart_selector) != base_hash: + changed = True + + assert changed, ( + f"Chart did not change after filter steps for selector {chart_selector}" + ) + + +PAGES = [ + ("summary", "/summary", ["#degree-days-chart-wrapper"]), + ("t_rh", "/t-rh", ["#heatmap", "#daily", "#yearly-chart"]), + ("sun", "/sun", ["#custom-sunpath", "#monthly-solar", "#cloud-cover"]), + ("wind", "/wind", ["#wind-rose", "#wind-speed", "#wind-direction"]), + ("psy", "/psy-chart", ["#psych-chart"]), + ("outdoor", "/outdoor", ["#utci-heatmap", "#utci-summary-chart"]), + ("explorer", "/explorer", ["#yearly-explore", "#query-daily", "#custom-heatmap"]), +] + + +@pytest.fixture(scope="function", autouse=True) +def _bootstrap_each(page: Page, base_url: str): + page.goto(base_url) + upload_epw_file(page) + yield + + +@pytest.mark.parametrize("page_name,path,selectors", PAGES, ids=[p[0] for p in PAGES]) +def test_time_filter_affects_page( + page: Page, base_url: str, page_name: str, path: str, selectors: List[str] +): + """Verify that charts react to month/hour filters (no screenshot, no timeout).""" + page.goto(f"{base_url}{path}") + open_tools_menu_and_filter_section(page) + + target = None + for sel in selectors: + loc = page.locator(sel).first + try: + loc.scroll_into_view_if_needed() + except Exception: + pass + if loc.is_visible(): + target = sel + break + if not target: + target = selectors[0] + page.locator(target).first.wait_for(state="visible") + + assert_chart_changes_by_three_steps(page, target) diff --git a/tests/test_natural_ventilation.py b/tests/test_natural_ventilation.py new file mode 100644 index 00000000..81724a92 --- /dev/null +++ b/tests/test_natural_ventilation.py @@ -0,0 +1,60 @@ +import pytest +from playwright.sync_api import Page, expect +from utils import upload_epw_file, open_tab + + +@pytest.fixture(scope="function", autouse=True) +def setup(page: Page, base_url): + """Setup: open app, upload EPW file, and go to Natural Ventilation tab""" + page.goto(base_url) + upload_epw_file(page) + open_tab(page, "Natural Ventilation") + yield + + +# -------------------- Test Title and Main Sections -------------------- +def test_nv_title(page: Page): + """Verify the title and main chart sections are visible""" + expect(page.get_by_text("Natural Ventilation Potential")).to_be_visible() + expect(page.locator("#nv-heatmap-chart")).to_be_visible() + expect(page.locator("#nv-bar-chart")).to_be_visible() + + +# -------------------- Test Input Controls -------------------- +def test_nv_input_controls(page: Page): + """Check input controls for temperature and dew point filters""" + expect(page.locator("#nv-dbt-filter")).to_be_visible() + expect(page.locator("#nv-dpt-filter")).to_be_visible() + expect(page.locator("#nv-tdb-min-val")).to_be_visible() + expect(page.locator("#nv-tdb-max-val")).to_be_visible() + expect(page.locator("#nv-dpt-max-val")).to_be_visible() + expect(page.locator("#enable-condensation")).to_be_visible() + + +# -------------------- Test Normalize Switch -------------------- +def test_nv_normalize_switch(page: Page): + """Ensure normalize switch and tooltip are visible""" + expect(page.get_by_text("Normalize data")).to_be_visible() + + +# -------------------- Test Apply Filter Button Interaction -------------------- +def test_nv_apply_filter(page: Page): + """Click Apply Filter and check that the heatmap updates""" + button = page.locator("#nv-dbt-filter") + expect(button).to_be_visible() + button.click() + expect(page.locator("#nv-heatmap-chart")).to_be_visible() + + +# -------------------- Test Condensation Checkbox Interaction -------------------- +def test_nv_condensation_checkbox(page: Page): + """Toggle condensation checkbox and verify dew point filter enables""" + checklist = page.locator("#enable-condensation") + button = page.locator("#nv-dpt-filter") + + # Initially should be disabled + expect(button).to_be_disabled() + + # Click to enable + checklist.get_by_label("Avoid condensation").check() + expect(button).to_be_enabled() diff --git a/tests/test_outdoor.py b/tests/test_outdoor.py new file mode 100644 index 00000000..37a6e4eb --- /dev/null +++ b/tests/test_outdoor.py @@ -0,0 +1,47 @@ +import pytest +from playwright.sync_api import Page, expect +from utils import upload_epw_file, open_tab + + +@pytest.fixture(scope="function", autouse=True) +def setup(page: Page, base_url): + """Setup: Go to base URL, upload EPW file, and open Outdoor page""" + page.goto(base_url) + upload_epw_file(page) + open_tab(page, "Outdoor Comfort") + yield + + +# -------------------- Test Title & Section Headings -------------------- +def test_outdoor_titles(page: Page): + """Verify key outdoor section titles are visible""" + expect(page.get_by_text("Select a scenario:")).to_be_visible() + expect(page.get_by_text("UTCI heatmap chart")).to_be_visible() + expect(page.get_by_text("UTCI thermal stress chart")).to_be_visible() + expect(page.get_by_text("Normalize data")).to_be_visible() + expect(page.get_by_text("The Best Weather Condition is:")).to_be_visible() + + +# -------------------- Test Image & Switch Components -------------------- +def test_outdoor_image_and_switch(page: Page): + """Verify that image and switch controls are visible""" + expect(page.locator("#image-selection")).to_be_visible() + + +# -------------------- Test Charts Rendering -------------------- +def test_outdoor_charts_render(page: Page): + """Ensure that main UTCI charts are rendered""" + expect(page.locator("#utci-heatmap")).to_be_visible() + expect(page.locator("#utci-category-heatmap")).to_be_visible() + expect(page.locator("#utci-summary-chart")).to_be_visible() + + +# -------------------- Test Dropdown Interaction -------------------- +def test_dropdown_interaction(page: Page): + """Switch scenario from dropdown and verify chart reloads""" + dropdown = page.locator("#outdoor-dropdown") + dropdown.click() + page.get_by_text("UTCI: Sun & no Wind", exact=True) + expect(page.locator("#utci-heatmap")).to_be_visible() + expect(page.locator("#utci-category-heatmap")).to_be_visible() + expect(page.locator("#utci-summary-chart")).to_be_visible() diff --git a/tests/test_psy-chart.py b/tests/test_psy-chart.py new file mode 100644 index 00000000..5698b82d --- /dev/null +++ b/tests/test_psy-chart.py @@ -0,0 +1,42 @@ +import pytest +from playwright.sync_api import Page, expect +from utils import upload_epw_file, open_tab + + +@pytest.fixture(scope="function", autouse=True) +def setup(page: Page, base_url): + """Setup: Go to base URL, upload EPW, and open /psy-chart page""" + page.goto(base_url) + upload_epw_file(page) + open_tab(page, "Psychrometric Chart") + yield + + +# -------------------- Test Page Title -------------------- +def test_psy_title(page: Page): + """Check if main Psy Chart title is visible""" + expect(page.get_by_role("heading", name="Psychrometric Chart")).to_be_visible() + + +# -------------------- Test Dropdowns and Sliders -------------------- +def test_psy_controls_visible(page: Page): + """Check all interactive inputs are visible""" + expect(page.locator("#psy-var-dropdown")).to_be_visible() + expect(page.locator("#psy-min-val")).to_be_visible() + expect(page.locator("#psy-max-val")).to_be_visible() + expect(page.get_by_role("button", name="Apply filter")).to_be_visible() + + +# -------------------- Test Psy Chart Render -------------------- +def test_psy_chart_rendered(page: Page): + """Check that the psy chart is rendered""" + expect(page.locator("#psych-chart")).to_be_visible() + + +# -------------------- Test Dropdown Interaction -------------------- +def test_dropdown_change_triggers_chart(page: Page): + """Switch dropdown to another variable and check chart still renders""" + dropdown = page.locator("#psy-color-by-dropdown") + dropdown.click() + page.get_by_text("Dry bulb temperature").click() + expect(page.locator("#psych-chart")).to_be_visible() diff --git a/tests/test_select.py b/tests/test_select.py new file mode 100644 index 00000000..26199724 --- /dev/null +++ b/tests/test_select.py @@ -0,0 +1,44 @@ +import pytest +from playwright.sync_api import Page, expect +from utils import upload_epw_file + + +@pytest.fixture(scope="function", autouse=True) +def setup(page: Page, base_url): + page.goto(f"{base_url}") + yield + + +# -------------------- Test Page Load -------------------- +def test_select_page_loads(page: Page): + """Verify that the Select Weather File page loads successfully""" + expect(page.locator("text=Select an EPW file from your computer")).to_be_visible() + expect(page.locator("#upload-data")).to_be_visible() + expect(page.locator("#alert")).to_contain_text("upload an EPW file") + + +# -------------------- Test Upload Button Visible -------------------- +def test_upload_button_visible(page: Page): + """Ensure the upload EPW button is visible""" + upload_button = page.locator("#upload-data-button") + expect(upload_button).to_be_visible() + expect(upload_button).to_contain_text("Select an EPW file") + + +# -------------------- Test Upload EPW File -------------------- +def test_upload_epw_file_success(page: Page): + """ + Simulate uploading an EPW file and verify success alert appears. + This test uses helper 'upload_epw_file' from utils.py. + """ + upload_epw_file(page) + alert_box = page.locator("#alert") + expect(alert_box).to_be_visible() + expect(alert_box).to_contain_text("EPW was successfully loaded!") + + +# -------------------- Test Map Renders -------------------- +def test_map_renders(page: Page): + """Verify that the map (epw_location.json plot) renders properly""" + map_container = page.locator("#tab-one-map") + expect(map_container).to_be_visible() diff --git a/tests/test_summary.py b/tests/test_summary.py new file mode 100644 index 00000000..72aad629 --- /dev/null +++ b/tests/test_summary.py @@ -0,0 +1,96 @@ +import pytest +from playwright.sync_api import Page, expect +from utils import upload_epw_file, open_tab + + +@pytest.fixture(scope="function", autouse=True) +def setup(page: Page, base_url): + """Setup: open base URL, upload EPW file, and navigate to /summary page""" + page.goto(base_url) + upload_epw_file(page) + open_tab(page, "Climate Summary") + yield + + +# -------------------- Test Section Titles -------------------- +def test_summary_titles(page: Page): + """Verify all main section headers are visible""" + expect(page.get_by_role("heading", name="Download")).to_be_visible() + expect(page.get_by_text("Heating and Cooling Degree Days")).to_be_visible() + expect(page.get_by_text("Climate Profiles")).to_be_visible() + + +# -------------------- Test Location Info Load -------------------- +def test_location_info_loaded(page: Page): + """Check if location info section displays properly""" + """Verify that location info section shows correct values""" + info_section = page.locator("#location-info") + expect(info_section).to_be_visible() + expected_texts = [ + "Location: Bologna Marconi AP, ITA", + "Longitude: 11.2969", + "Latitude: 44.5308", + "Elevation above sea level: 37.0 m", + "This file is based on data collected between 2004 and 2018", + "Köppen-Geiger climate zone: Cfa. Humid subtropical, no dry season.", + "Average yearly temperature: 14.5 °C", + "Hottest yearly temperature (99%): 34.0 °C", + "Coldest yearly temperature (1%): -2.0 °C", + "Annual cumulative horizontal solar radiation: 1546.12 kWh/m2", + "Percentage of diffuse horizontal solar radiation: 39.4 %", + ] + for text in expected_texts: + expect(info_section).to_contain_text(text) + + expect(page.locator("#world-map")).to_be_visible() + + +# -------------------- Test Download Buttons -------------------- +def test_download_buttons(page: Page, tmp_path): + """Verify that both download buttons are visible and clickable""" + + # Locate both download buttons on the page + epw_button = page.get_by_role("button", name="Download EPW") + clima_button = page.get_by_role("button", name="Download Clima dataframe") + + # Ensure the buttons are visible + expect(epw_button).to_be_visible() + expect(clima_button).to_be_visible() + + +# -------------------- Test Degree Day Setpoints and Chart -------------------- +def test_degree_day_chart_visible(page: Page): + """Ensure degree day chart and input controls are visible""" + expect(page.locator("#input-hdd-set-point")).to_be_visible() + expect(page.locator("#input-cdd-set-point")).to_be_visible() + expect(page.locator("#submit-set-points")).to_be_visible() + expect(page.locator("#degree-days-chart-wrapper")).to_be_visible() + + +# -------------------- Test Climate Profile Graphs -------------------- +def test_climate_profile_graphs(page: Page): + """Verify that the four climate profile graphs are visible""" + expect(page.locator("#temp-profile-graph")).to_be_visible() + expect(page.locator("#humidity-profile-graph")).to_be_visible() + expect(page.locator("#solar-radiation-graph")).to_be_visible() + expect(page.locator("#wind-speed-graph")).to_be_visible() + + +# -------------------- Test SI/IP System Toggle -------------------- +def test_unit_switch(page: Page): + """ + Verify that the banner radio buttons (SI/IP) correctly toggle. + """ + nav_controls = page.locator("#nav-group-controls") + nav_controls.click(force=True) + + # Click the "IP" option + ip_button = page.get_by_text("IP", exact=True) + ip_button.scroll_into_view_if_needed() + ip_button.wait_for(state="visible") + ip_button.click(force=True) + + info_section = page.locator("#location-info") + expect(info_section).to_contain_text("°F") + expect(info_section).to_contain_text("ft") + expect(info_section).to_contain_text("kBtu/ft2") diff --git a/tests/test_sun.py b/tests/test_sun.py new file mode 100644 index 00000000..347e9bff --- /dev/null +++ b/tests/test_sun.py @@ -0,0 +1,62 @@ +import pytest +from playwright.sync_api import Page, expect +from utils import upload_epw_file, open_tab + + +@pytest.fixture(scope="function", autouse=True) +def setup(page: Page, base_url): + """Setup: open base URL, upload EPW file, and navigate to /sun page""" + page.goto(base_url) + upload_epw_file(page) + open_tab(page, "Sun and Clouds") + yield + + +# -------------------- Test Page Titles -------------------- +def test_sun_titles(page: Page): + """Verify main Sun page section titles""" + expect(page.get_by_text("Sun path chart")).to_be_visible() + expect( + page.get_by_text("Global and Diffuse Horizontal Solar Radiation") + ).to_be_visible() + expect(page.get_by_text("Cloud coverage")).to_be_visible() + expect(page.get_by_text("Daily charts")).to_be_visible() + + +# -------------------- Test Dropdown Controls -------------------- +def test_sun_dropdown_controls(page: Page): + """Check if all dropdowns for Sun page controls are visible""" + expect(page.locator("#custom-sun-view-dropdown")).to_be_visible() + expect(page.locator("#custom-sun-var-dropdown")).to_be_visible() + expect(page.locator("#sun-explore-dropdown")).to_be_visible() + + +# -------------------- Test Graph Visibility -------------------- +def test_sun_graphs_visible(page: Page): + """Ensure all main Sun graphs are rendered correctly""" + expect(page.locator("#custom-sunpath")).to_be_visible() # Sun path chart + expect(page.locator("#monthly-solar")).to_be_visible() # Global & Diffuse + expect(page.locator("#cloud-cover")).to_be_visible() # Cloud coverage + expect(page.locator("#sun-daily")).to_be_visible() # Daily line chart + expect(page.locator("#sun-heatmap")).to_be_visible() # Daily heatmap + + +# -------------------- Test Sun Path Chart View and Variable Switching -------------------- +def test_sun_path_switch_view_and_variable(page: Page): + """Switch view and variable in dropdown and check chart re-renders in sun path chart""" + view_dropdown = page.locator("#custom-sun-view-dropdown") + view_dropdown.click() + page.get_by_text("Cartesian").click() + var_dropdown = page.locator("#custom-sun-var-dropdown") + var_dropdown.click() + page.get_by_text("Relative humidity").click() + expect(page.locator("#custom-sunpath")).to_be_visible() + + +# -------------------- Test Daily Chart Variable Switching -------------------- +def test_daily_switch_variable(page: Page): + """Switch view and variable in dropdown and check chart re-renders in daily chart""" + var_dropdown = page.locator("#sun-explore-dropdown") + var_dropdown.click() + page.get_by_text("Direct normal illuminance").click() + expect(page.locator("#sun-daily")).to_be_visible() diff --git a/tests/test_t_rh.py b/tests/test_t_rh.py new file mode 100644 index 00000000..11b0a320 --- /dev/null +++ b/tests/test_t_rh.py @@ -0,0 +1,69 @@ +import pytest +from playwright.sync_api import Page, expect +from utils import upload_epw_file, open_tab + + +@pytest.fixture(scope="function", autouse=True) +def setup(page: Page, base_url): + """Setup: open base URL, upload EPW file, and navigate to /t_rh page""" + page.goto(base_url) + upload_epw_file(page) + open_tab(page, "Temperature and Humidity") + yield + + +# -------------------- Test Section Headings -------------------- +def test_section_headings(page: Page): + """Check section titles are visible""" + expect(page.get_by_text("Yearly Chart")).to_be_visible() + expect(page.get_by_text("Daily chart")).to_be_visible() + expect(page.get_by_text("Heatmap chart")).to_be_visible() + expect(page.get_by_text("Descriptive statistics")).to_be_visible() + + +# -------------------- Test Chart Rendering -------------------- +def test_chart_visibility(page: Page): + """Ensure all charts are rendered""" + expect(page.locator("#yearly-chart")).to_be_visible() + expect(page.locator("#daily")).to_be_visible() + expect(page.locator("#heatmap")).to_be_visible() + + +# -------------------- Test Table Rendering -------------------- +def test_statistics_table(page: Page): + """Ensure the statistics table is visible""" + table = page.locator("#table-tmp-hum") + expect(table).to_be_visible() + + expected_columns = ["month", "mean", "min", "max", "std", "Jun", "Year"] + for col in expected_columns: + expect(table).to_contain_text(col) + + +# -------------------- Test Variable Switching -------------------- +def test_switch_variable_and_rerender(page: Page): + """Switch variable in dropdown and check chart re-renders""" + dropdown = page.locator("#dropdown") + dropdown.click() + page.get_by_text("Relative humidity").click() + # Re-check visibility of charts after switching variable + expect(page.locator("#yearly-chart")).to_be_visible() + expect(page.locator("#daily")).to_be_visible() + expect(page.locator("#heatmap")).to_be_visible() + + +# -------------------- Test Global/Local System Toggle -------------------- +def test_banner_unit_switch(page: Page): + """ + Verify that the banner radio buttons (Global/Local) correctly toggle. + """ + nav_controls = page.locator("#nav-group-controls") + nav_controls.click(force=True) + + # Click the "Global" option + global_button = page.get_by_text("Global", exact=True) + global_button.scroll_into_view_if_needed() + global_button.wait_for(state="visible") + global_button.click(force=True) + + expect(page.get_by_text("-40", exact=False)).to_be_visible() diff --git a/tests/test_wind.py b/tests/test_wind.py new file mode 100644 index 00000000..063d66b0 --- /dev/null +++ b/tests/test_wind.py @@ -0,0 +1,55 @@ +import pytest +from playwright.sync_api import Page, expect +from utils import upload_epw_file, open_tab + + +@pytest.fixture(scope="function", autouse=True) +def setup(page: Page, base_url): + """Setup: open base URL, upload EPW file, and navigate to /wind page""" + page.goto(base_url) + upload_epw_file(page) + open_tab(page, "Wind") + yield + + +# -------------------- Test Wind Page Titles -------------------- +def test_wind_titles(page: Page): + """Verify main titles of Wind page are visible""" + expect(page.get_by_text("Annual Wind Rose")).to_be_visible() + expect(page.get_by_text("Seasonal Wind Rose")).to_be_visible() + expect(page.get_by_text("Daily Wind Rose")).to_be_visible() + + +# -------------------- Test Wind Rose Graphs -------------------- +def test_wind_rose_graphs_visible(page: Page): + """Check that all Wind Rose charts are rendered and visible""" + wind_rose_ids = [ + "#wind-rose", + "#winter-wind-rose", + "#spring-wind-rose", + "#summer-wind-rose", + "#fall-wind-rose", + "#morning-wind-rose", + "#noon-wind-rose", + "#night-wind-rose", + ] + for wid in wind_rose_ids: + expect(page.locator(wid)).to_be_visible() + + +# -------------------- Test Wind Rose Text -------------------- +def test_wind_rose_texts(page: Page): + """Verify that all Wind Rose description texts""" + expected_texts = { + "#winter-wind-rose-text": "Dec and Feb", + "#spring-wind-rose-text": "Mar and May", + "#summer-wind-rose-text": "Jun and Aug", + "#fall-wind-rose-text": "Sep and Dec", + "#morning-wind-rose-text": "6:00 hours and 13:00 hours", + "#noon-wind-rose-text": "14:00 hours and 21:00 hours", + "#night-wind-rose-text": "22:00 hours and 5:00 hours", + } + + for cid, expected in expected_texts.items(): + element = page.locator(cid) + expect(element).to_contain_text(expected) diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 00000000..ec50a45d --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,37 @@ +from pathlib import Path +from playwright.sync_api import Page, expect + + +def upload_epw_file(page: Page, filename: str = "test.epw"): + """ + Upload an EPW file and verify that the success message appears. + + Parameters: + - page: The Playwright Page object. + - filename: Path to the EPW file (defaults to tests/test.epw). + """ + epw_path = Path(filename).resolve() + page.set_input_files('input[type="file"]', str(epw_path)) + + # Verify that the upload success messages are displayed + expect(page.get_by_text("The EPW was successfully loaded!")).to_be_visible() + expect( + page.get_by_text("Current Location: Bologna Marconi AP, ITA") + ).to_be_visible() + + +def open_tab(page: Page, tab_name: str): + """ + Open a specific tab from the sidebar navigation (default expanded version). + Works reliably for Mantine NavLink structure. + """ + # Find the navigation link container whose id starts with "nav-" and text matches + nav_link = page.locator(f'[id^="nav-"] >> text="{tab_name}"').first + + # Go up to the clickable element ( or