diff --git a/config.py b/config.py index bc79454..b53ce10 100644 --- a/config.py +++ b/config.py @@ -54,7 +54,7 @@ class Assets: class PageInfo: """Stores page names and orders for registration.""" - SELECT_NAME = "Select Weather File" + SELECT_NAME = "Select weather file" SELECT_ORDER = 0 SUMMARY_NAME = "Climate Summary" SUMMARY_ORDER = 1 diff --git a/pages/lib/layout.py b/pages/lib/layout.py index 523b9e6..93374f3 100644 --- a/pages/lib/layout.py +++ b/pages/lib/layout.py @@ -14,7 +14,6 @@ class NavBarIcons: _ICON_MAP = { - "Select Weather File": "tabler:upload", "Climate Summary": "tabler:chart-bar", "Temperature and Humidity": "tabler:temperature", "Sun and Clouds": "tabler:sun", @@ -26,7 +25,6 @@ class NavBarIcons: "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"] @@ -49,14 +47,7 @@ def create_tools_filter_components(): return dmc.Stack( id=ElementIds.TOOLS_MONTH_HOUR_SECTION, children=[ - dmc.Divider(label="Filter function", size="xs", color="blue"), - dmc.Button( - "Apply month and hour filter", - id=ElementIds.TOOLS_APPLY_MONTH_HOUR_FILTER, - color="blue", - variant="light", - size="xs", - ), + dmc.Divider(label="Filters", size="xs", color="blue"), # Month controls dmc.Text("Month Range:", size="xs", c="dimmed"), dcc.RangeSlider( @@ -113,6 +104,13 @@ def create_tools_filter_components(): ], justify="flex-end", ), + dmc.Button( + "Apply month and hour filter", + id=ElementIds.TOOLS_APPLY_MONTH_HOUR_FILTER, + color="blue", + variant="filled", + size="xs", + ), ], gap="xs", p="xs", @@ -137,7 +135,28 @@ def create_navbar(): } } - # Secondary Menu + # Select weather file - top-level menu item + select_weather_file_page = next( + ( + page + for page in dash.page_registry.values() + if page[Variables.NAME.col_name] == "Select weather file" + ), + None, + ) + select_weather_file_link = ( + dmc.NavLink( + label=select_weather_file_page[Variables.NAME.col_name], + href=select_weather_file_page[Variables.PATH.col_name], + id=f"nav-{select_weather_file_page[Variables.PATH.col_name].replace('/', '')}", + active=False, + styles=nav_link_styles, + ) + if select_weather_file_page + else None + ) + + # Secondary Menu - exclude "Select weather file" as it will be a top-level menu sub_links = [ dmc.NavLink( label=page[Variables.NAME.col_name], @@ -150,11 +169,12 @@ def create_navbar(): styles=nav_link_styles, ) for page in dash.page_registry.values() - if page[Variables.NAME.col_name] not in ["404", "Changelog"] + if page[Variables.NAME.col_name] + not in ["404", "Changelog", "Select weather file"] ] parent_group = dmc.NavLink( - label="Pages Menu", + label="Visualize weather file", children=sub_links, id=ElementIds.NAV_GROUP_MAIN, variant="light", @@ -168,17 +188,11 @@ def create_navbar(): controls_stack = dmc.Stack( gap="xs", - py="xs", + p="xs", children=[ + dmc.Divider(label="Units and Ranges", size="xs", color="blue"), dmc.Tooltip( - label=dmc.Stack( - gap="xs", - children=[ - dmc.Text( - "You can choose value ranges between Global and Local" - ), - ], - ), + label=dmc.Text("You can choose value ranges between Global and Local"), position="right", withArrow=True, children=dmc.SegmentedControl( @@ -189,18 +203,13 @@ def create_navbar(): {"label": "Global", "value": "global"}, {"label": "Local", "value": "local"}, ], - w=220, + w=210, size="sm", styles=segmented_control_styles, ), ), dmc.Tooltip( - label=dmc.Stack( - gap="xs", - children=[ - dmc.Text("You can choose units between SI and IP"), - ], - ), + label=dmc.Text("You can choose units between SI and IP"), position="right", withArrow=True, children=dmc.SegmentedControl( @@ -211,7 +220,7 @@ def create_navbar(): {"label": "SI", "value": UnitSystem.SI}, {"label": "IP", "value": UnitSystem.IP}, ], - w=220, + w=210, size="sm", styles=segmented_control_styles, ), @@ -223,11 +232,12 @@ def create_navbar(): # Tools controls_group = dmc.NavLink( - label="Tools Menu", - children=[controls_stack, filter_components], + label="Filters and units", + children=[filter_components, controls_stack], id=ElementIds.NAV_GROUP_CONTROLS, variant="light", childrenOffset=0, + opened=True, ) # Documentation @@ -240,7 +250,12 @@ def create_navbar(): ) return dmc.ScrollArea( - children=[parent_group, controls_group, doc_link], + children=[ + select_weather_file_link, + parent_group, + controls_group, + doc_link, + ], ) diff --git a/pages/lib/template_graphs.py b/pages/lib/template_graphs.py index b6ab5b5..d76665e 100644 --- a/pages/lib/template_graphs.py +++ b/pages/lib/template_graphs.py @@ -405,7 +405,9 @@ def heatmap_with_filter( ), ) - if global_local == "global": + # For category variables (e.g., UTCI categories), always use global range + # to ensure consistent color mapping regardless of data range + if "_categories" in var or global_local == "global": # Set Global values for Max and minimum range_z = var_range else: @@ -712,7 +714,9 @@ def wind_rose(df, title, month, hour, labels, si_ip, skip_time_filter=False): spd_colors = wind_speed_variable.get_color() spd_unit = wind_speed_variable.get_unit(si_ip) - spd_bins = WIND_ROSE_BINS + spd_bins = list( + WIND_ROSE_BINS + ) # Create a copy to avoid modifying the global constant if si_ip == UnitSystem.IP: spd_bins = convert_bins(spd_bins) @@ -810,12 +814,18 @@ def wind_rose(df, title, month, hour, labels, si_ip, skip_time_filter=False): def convert_bins(sbins): - i = 0 + """Convert wind speed bins from m/s to fpm (feet per minute). + + Returns a new list without modifying the input list. + """ + result = [] for x in sbins: - x = x * 196.85039370078738 - sbins[i] = round(x, 1) - i = i + 1 - return sbins + if np.isfinite(x): + converted = round(x * 196.85039370078738, 1) + result.append(converted) + else: + result.append(x) # Preserve np.inf + return result def thermal_stress_stacked_barchart( diff --git a/pages/select.py b/pages/select.py index 2b61da8..d295c06 100644 --- a/pages/select.py +++ b/pages/select.py @@ -248,7 +248,6 @@ def switch_si_ip(_, si_ip_input, url_store, lines): @callback( [ - Output(ElementIds.NAV, "disabled"), Output(ElementIds.NAV_SUMMARY, "disabled"), Output(ElementIds.NAV_T_RH, "disabled"), Output(ElementIds.NAV_SUN, "disabled"), @@ -277,7 +276,6 @@ def enable_tabs_when_data_is_loaded(meta, data): True, True, True, - True, default, ) else: @@ -290,7 +288,6 @@ def enable_tabs_when_data_is_loaded(meta, data): False, False, False, - False, "Current Location: " + meta[Variables.CITY.col_name] + ", " diff --git a/pages/sun.py b/pages/sun.py index 650fe97..2f7c831 100644 --- a/pages/sun.py +++ b/pages/sun.py @@ -286,7 +286,7 @@ def sun_path_chart(_, view, var, global_local, global_filter_data, df, meta, si_ units = "" if var == "None" else generate_units(si_ip) if view == "polar": return dcc.Graph( - style={"width": "100%", "height": "520px"}, + style={"maxWidth": "50em", "height": "520px"}, config=generate_chart_name( TabNames.SPHERICAL_SUNPATH, meta, custom_inputs, units ), @@ -294,7 +294,7 @@ def sun_path_chart(_, view, var, global_local, global_filter_data, df, meta, si_ ) else: return dcc.Graph( - style={"width": "100%", "height": "520px"}, + style={"maxWidth": "50em", "height": "520px"}, config=generate_chart_name( TabNames.CARTESIAN_SUNPATH, meta, custom_inputs, units ), diff --git a/tests/test_filter.py b/tests/test_filter.py index 7347e93..95a3367 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -26,19 +26,8 @@ def ensure_local_mode_and_invert_off(page: Page): 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() + apply_btn = page.get_by_text("Apply month and hour filter", exact=False) + expect(apply_btn.first).to_be_visible() ensure_local_mode_and_invert_off(page) diff --git a/tests/test_summary.py b/tests/test_summary.py index 72aad62..f2fbc38 100644 --- a/tests/test_summary.py +++ b/tests/test_summary.py @@ -86,11 +86,12 @@ def test_unit_switch(page: Page): # Click the "IP" option ip_button = page.get_by_text("IP", exact=True) + expect(ip_button).to_be_visible() 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") + expect(info_section.get_by_text("°F")).to_be_visible + expect(info_section.get_by_text("ft")).to_be_visible + expect(info_section.get_by_text("kBtu/ft2")).to_be_visible