import base64 import re from pathlib import Path import pandas as pd import streamlit as st from PIL import Image from data_loader import get_all_sections, load_material_data import streamlit.components.v1 as components def clear_search(): """Clear the search box and search term. Called as a callback from other widgets.""" st.session_state._search_term = None st.session_state["_pending_clear_search"] = True def switch_tab(index, clear=True): count = st.session_state.get('_tab_switch_count', 0) + 1 st.session_state['_tab_switch_count'] = count components.html(f""" """, height=0) st.session_state.current_tab = index if clear: clear_search() st.markdown( """ """, unsafe_allow_html=True, ) @st.cache_data def load_data(material_type: str) -> pd.DataFrame: return load_material_data(material_type) @st.cache_data def load_all_data() -> pd.DataFrame: frames = [] for material_type in ["Composites", "Polymers", "Fibers"]: frame = load_data(material_type) frame["_class"] = material_type frames.append(frame) return pd.concat(frames, ignore_index=True) def extract_matrix_fiber(abbr: str): if not isinstance(abbr, str): return None, None text = abbr.lower() matrix_map = { "epoxy": "Epoxy", "cyanate ester": "Cyanate Ester", "cynate ester": "Cyanate Ester", "polypropylene": "Polypropylene", "pp": "Polypropylene", "peek": "PEEK", "pei": "PEI", "nylon": "Nylon", "pa6": "PA6", "polyester": "Polyester", "vinyl ester": "Vinyl Ester", "phenolic": "Phenolic", } fiber_map = { "carbon": "Carbon Fiber", "glass": "Glass Fiber", "e-glass": "E-Glass Fiber", "s-glass": "S-Glass Fiber", "aramid": "Aramid Fiber", "kevlar": "Kevlar Fiber", "basalt": "Basalt Fiber", "natural": "Natural Fiber", } matrix = next((value for key, value in matrix_map.items() if key in text), None) fiber = next((value for key, value in fiber_map.items() if key in text), None) return matrix, fiber def toggle_class(material_class: str): if st.session_state.active_classes == [material_class]: st.session_state.active_classes = [] else: st.session_state.active_classes = [material_class] st.session_state.current_page = 0 st.session_state.selected_row = None st.session_state["last_synced_abbr"] = None # Clear the dataframe selection for page 0 df_key = f"materials_df_0" if df_key in st.session_state: st.session_state[df_key] = {"selection": {"rows": [], "columns": [], "cells": []}} st.session_state["_switch_to_tab"] = 0 clear_search() def visible_page_numbers(current_page: int, total_pages: int): if total_pages <= 6: return list(range(total_pages)) pages = {0, 1, 2, current_page, total_pages - 1} if current_page - 1 > 2: pages.add(current_page - 1) if current_page + 1 < total_pages - 1: pages.add(current_page + 1) return sorted(pages) defaults = { "active_classes": [], "selected_props": [], "selected_matrix": "All", "selected_fiber": "All", "selected_row": None, "current_page": 0, "_search_term": None, "top_search_input": "", "inspect_section": None, "inspect_property": None, "_reset_prop_checks": False, } for key, value in defaults.items(): if key not in st.session_state: st.session_state[key] = value if st.session_state.get("_last_page") != "Categorized_Search": st.session_state._search_term = None st.session_state.top_search_input = "" st.session_state._last_page = "Categorized_Search" if "material_type" in st.session_state: incoming_type = st.session_state.pop("material_type") if incoming_type in ["Composites", "Polymers", "Fibers"]: st.session_state.active_classes = [incoming_type] if "selected_section" in st.session_state: st.session_state.selected_props = [st.session_state.pop("selected_section")] st.session_state._reset_prop_checks = True if "search_term" in st.session_state: incoming_term = st.session_state.pop("search_term") st.session_state._search_term = incoming_term st.session_state.top_search_input = incoming_term all_data = load_all_data() if "user_uploaded_data" in st.session_state: uploaded = st.session_state["user_uploaded_data"].copy() uploaded["_class"] = uploaded["material_class"].map( {"Polymer": "Polymers", "Fiber": "Fibers", "Composite": "Composites"} ) all_data = pd.concat([all_data, uploaded], ignore_index=True) st.session_state["base_data"] = all_data meta = ( all_data[["material_abbreviation", "material_name", "_class"]] .fillna("") .drop_duplicates(subset=["material_abbreviation"]) .reset_index(drop=True) ) matrix_fiber = meta["material_abbreviation"].apply(extract_matrix_fiber) meta["Matrix"] = matrix_fiber.apply( lambda pair: pair[0] if isinstance(pair, tuple) and len(pair) >= 2 else None ) meta["Fiber"] = matrix_fiber.apply( lambda pair: pair[1] if isinstance(pair, tuple) and len(pair) >= 2 else None ) all_sections = get_all_sections() if st.session_state._reset_prop_checks: selected = set(st.session_state.selected_props) for index, section in enumerate(all_sections): st.session_state[f"prop_check_{index}"] = section in selected st.session_state._reset_prop_checks = False filtered_meta = meta.copy() if st.session_state.active_classes: filtered_meta = filtered_meta[filtered_meta["_class"].isin(st.session_state.active_classes)] if st.session_state.selected_matrix != "All": filtered_meta = filtered_meta[filtered_meta["Matrix"] == st.session_state.selected_matrix] if st.session_state.selected_fiber != "All": filtered_meta = filtered_meta[filtered_meta["Fiber"] == st.session_state.selected_fiber] if st.session_state._search_term: term = st.session_state._search_term try: pattern = re.compile(term, re.IGNORECASE) except re.error: pattern = re.compile(re.escape(term), re.IGNORECASE) filtered_meta = filtered_meta[ filtered_meta["material_abbreviation"].astype(str).str.contains(pattern) | filtered_meta["material_name"].astype(str).str.contains(pattern) ] if st.session_state.selected_props: valid_abbr = all_data[ all_data["section"].isin(st.session_state.selected_props) & all_data["value"].notna() ]["material_abbreviation"].unique() filtered_meta = filtered_meta[filtered_meta["material_abbreviation"].isin(valid_abbr)] filtered_meta = filtered_meta.reset_index(drop=True) PAGE_SIZE = 50 total = len(filtered_meta) total_pages = max(1, (total + PAGE_SIZE - 1) // PAGE_SIZE) st.session_state.current_page = min(st.session_state.current_page, total_pages - 1) start = st.session_state.current_page * PAGE_SIZE end = start + PAGE_SIZE page_meta = filtered_meta.iloc[start:end].reset_index(drop=True) if st.session_state.pop("_restore_selection_highlight", False): st.session_state.pop("_search_just_changed", None) if st.session_state.selected_row: abbr = st.session_state.selected_row[0] match = page_meta[page_meta["material_abbreviation"] == abbr] if not match.empty: st.session_state["pending_row_select"] = match.index[0] else: st.session_state.pop("pending_row_select", None) # โ† clear stale index st.session_state["_clear_df_selection"] = True # โ† clear wrong highlight st.session_state["_search_just_changed"] = True left_col, right_col = st.columns([1.03, 3.07], gap="small") with left_col: with st.container(border=True): logo_html = "" logo_path = Path("logo.png") if logo_path.exists(): with logo_path.open("rb") as file_handle: logo_b64 = base64.b64encode(file_handle.read()).decode() logo_html = ( f"" ) st.markdown( f"", unsafe_allow_html=True, ) st.markdown("
๐Ÿงฉ Material Class
", unsafe_allow_html=True) cls_a, cls_b = st.columns(2) with cls_a: if st.button( "Composites", key="class_comp", use_container_width=True, type="primary" if "Composites" in st.session_state.active_classes else "secondary", ): toggle_class("Composites") st.rerun() with cls_b: if st.button( "Polymers", key="class_poly", use_container_width=True, type="primary" if "Polymers" in st.session_state.active_classes else "secondary", ): toggle_class("Polymers") st.rerun() if st.button( "Fibers", key="class_fib", use_container_width=True, type="primary" if "Fibers" in st.session_state.active_classes else "secondary", ): toggle_class("Fibers") st.rerun() st.markdown("
๐Ÿงช Composition
", unsafe_allow_html=True) composite_meta = meta[meta["_class"] == "Composites"] matrix_options = ["All"] + sorted([item for item in composite_meta["Matrix"].dropna().unique() if item]) fiber_options = ["All"] + sorted([item for item in composite_meta["Fiber"].dropna().unique() if item]) composition_disabled = st.session_state.active_classes != ["Composites"] st.markdown("
๐Ÿงฑ Matrix
", unsafe_allow_html=True) matrix_value = st.selectbox( "Matrix", matrix_options, index=matrix_options.index(st.session_state.selected_matrix) if st.session_state.selected_matrix in matrix_options else 0, key="matrix_select", disabled=composition_disabled, ) st.markdown("
๐Ÿงต Fiber
", unsafe_allow_html=True) fiber_value = st.selectbox( "Fiber", fiber_options, index=fiber_options.index(st.session_state.selected_fiber) if st.session_state.selected_fiber in fiber_options else 0, key="fiber_select", label_visibility="collapsed", disabled=composition_disabled, ) if matrix_value != st.session_state.selected_matrix: st.session_state.selected_matrix = matrix_value st.session_state.current_page = 0 st.rerun() if fiber_value != st.session_state.selected_fiber: st.session_state.selected_fiber = fiber_value st.session_state.current_page = 0 st.rerun() #st.markdown("
๐Ÿ“‹ Property Types
", unsafe_allow_html=True) #selected_props = [] #with st.container(height=480): # for index, section in enumerate(all_sections): # key = f"prop_check_{index}" # if key not in st.session_state: # st.session_state[key] = section in st.session_state.selected_props # if st.checkbox(section, key=key): # selected_props.append(section) #if selected_props != st.session_state.selected_props: # st.session_state.selected_props = selected_props # st.session_state.current_page = 0 # st.rerun() if st.button( "๐Ÿ”Ž Inspect", key="inspect_btn", use_container_width=True, type="primary", disabled=st.session_state.selected_row is None, ): st.info("Open the Inspect tab on the right panel.") st.session_state["_switch_to_tab"] = 1 if st.session_state.selected_row: selected_abbr, selected_name = st.session_state.selected_row st.markdown( f"
Selected
{selected_name}
{selected_abbr}
", unsafe_allow_html=True, ) if st.session_state.pop("_pending_clear_search", False): st.session_state.top_search_input = "" if st.session_state._search_term != st.session_state.get("_previous_search_term"): if st.session_state.top_search_input: st.session_state._search_term = st.session_state.top_search_input if st.session_state._search_term: st.session_state.selected_row = None st.session_state["_clear_df_selection"] = True switch_tab(0, clear=False) st.session_state["_previous_search_term"] = st.session_state._search_term with right_col: with st.container(border=True): with st.container(key="top_search_row"): input_col, btn_col = st.columns([0.82, 0.18], gap="small") with input_col: search_query = st.text_input( label="Search", placeholder="Search material name or abbreviation...", label_visibility="collapsed", key="top_search_input", on_change=lambda: None, ) with btn_col: search_clicked = st.button("Search", key="top_search_btn", width="stretch") current_input = (search_query or "").strip() previous_term = st.session_state._search_term or "" if search_clicked: if current_input: st.session_state["_switch_to_tab"] = 0 st.session_state._search_term = current_input if current_input else None st.session_state.current_page = 0 if current_input: st.session_state.selected_row = None st.session_state["_clear_df_selection"] = True st.rerun() elif current_input != previous_term: st.session_state._search_term = current_input if current_input else None st.session_state.current_page = 0 if current_input: st.session_state.selected_row = None st.session_state["_clear_df_selection"] = True else: st.session_state.pop("pending_row_select", None) # โ† clear stale index #st.session_state["_clear_df_selection"] = True # โ† clear visual highlight st.session_state["_restore_selection_highlight"] = True st.session_state["_search_just_changed"] = True st.rerun() with st.container(border=True): st.markdown( """
INVENTORY / MATERIALS DATABASE
Materials Database
""", unsafe_allow_html=True, ) tab_materials, tab_inspect = st.tabs( ["All Materials", "Inspect"] ) if st.session_state.get("_switch_to_tab") is not None: tab_index = st.session_state["_switch_to_tab"] switch_tab(tab_index, clear=(tab_index != 1)) del st.session_state["_switch_to_tab"] with tab_materials: filter_label = ( ", ".join(st.session_state.active_classes) if st.session_state.active_classes else "All Materials" ) shown_start = start + 1 if total > 0 else 0 shown_end = min(end, total) row_left, row_right = st.columns([2.2, 1.2]) with row_left: st.markdown(f"
{filter_label}
", unsafe_allow_html=True) with row_right: st.markdown( f"
Showing {shown_start}-{shown_end} of {total} materials
", unsafe_allow_html=True, ) selected_abbr = st.session_state.selected_row[0] if st.session_state.selected_row else None class_map = { "Composites": "๐Ÿ”ต COMPOSITE", "Polymers": "๐ŸŸข POLYMER", "Fibers": "๐ŸŸ  FIBER", } if not page_meta.empty: table_df = page_meta.copy() table_df["Class"] = table_df["_class"].map(class_map) table_df["Actions"] = "" table_df = table_df[ [ "material_name", "material_abbreviation", "Class", "Actions"] ].rename( columns={ "material_name": "Material Name", "material_abbreviation": "Abbreviation", } ) else: table_df = pd.DataFrame( columns=["Select", "Material Name", "Abbreviation", "Class", "Actions"] ) df_key = f"materials_df_{st.session_state.current_page}" if "pending_row_select" in st.session_state: st.session_state[df_key] = {"selection": {"rows": [st.session_state.pending_row_select], "columns": [], "cells": []}} del st.session_state["pending_row_select"] if st.session_state.pop("_clear_df_selection", False): st.session_state[df_key] = {"selection": {"rows": [], "columns": [], "cells": []}} event = st.dataframe( table_df, key=df_key, width="stretch", hide_index=True, height=400, on_select="rerun", selection_mode=["single-cell", "single-row"], column_config={ "Material Name": st.column_config.TextColumn("MATERIAL NAME", width="large"), "Abbreviation": st.column_config.TextColumn("ABBREVIATION", width="medium"), "Class": st.column_config.TextColumn("CLASS", width="small"), "Actions": st.column_config.TextColumn("ACTIONS", width="small"), }, ) selected_rows = event.selection.rows selected_cells = event.selection.cells if selected_rows: row_idx = selected_rows[0] if row_idx >= len(page_meta): st.session_state.selected_row = None st.session_state["_clear_df_selection"] = True st.rerun() chosen = page_meta.iloc[row_idx] abbr = chosen["material_abbreviation"] name = chosen["material_name"] if not (st.session_state.selected_row and st.session_state.selected_row[0] == abbr): st.session_state.selected_row = (abbr, name) st.rerun() else: if ( st.session_state.selected_row is not None and not selected_cells and not st.session_state.pop("_search_just_changed", False) ): st.session_state.selected_row = None st.rerun() if selected_cells: row_idx = selected_cells[0][0] if row_idx >= len(page_meta): st.session_state["_clear_df_selection"] = True st.rerun() chosen = page_meta.iloc[row_idx] abbr = chosen["material_abbreviation"] name = chosen["material_name"] if st.session_state.selected_row and st.session_state.selected_row[0] == abbr: st.session_state.selected_row = None st.session_state["_clear_df_selection"] = True st.rerun() if st.session_state.selected_row is None or st.session_state.selected_row[0] != abbr: st.session_state.selected_row = (abbr, name) st.session_state["pending_row_select"] = row_idx st.rerun() info_col, nav_col = st.columns([2.4, 2.0]) with info_col: st.markdown( f"
Showing {shown_start}-{shown_end} of {total} materials | Items per page: {PAGE_SIZE}
", unsafe_allow_html=True, ) with nav_col: nav_items = [] nav_items.append(("<<", 0, st.session_state.current_page == 0, False)) nav_items.append(("<", max(0, st.session_state.current_page - 1), st.session_state.current_page == 0, False)) visible_pages = visible_page_numbers(st.session_state.current_page, total_pages) previous = -1 for number in visible_pages: if previous >= 0 and number - previous > 1: nav_items.append(("...", None, True, False)) nav_items.append( ( str(number + 1), number, False, number == st.session_state.current_page, ) ) previous = number nav_items.append( ( ">", min(total_pages - 1, st.session_state.current_page + 1), st.session_state.current_page >= total_pages - 1, False, ) ) nav_items.append( ( ">>", total_pages - 1, st.session_state.current_page >= total_pages - 1, False, ) ) nav_columns = st.columns(len(nav_items)) for idx, (column, item) in enumerate(zip(nav_columns, nav_items)): label, target_page, disabled, active = item with column: if label == "...": st.markdown("
...
", unsafe_allow_html=True) else: if st.button( label, key=f"page_btn_{idx}_{label}_{target_page}", width="stretch", disabled=disabled, type="primary" if active else "secondary", ): old_page = st.session_state.current_page st.session_state.current_page = target_page st.session_state.selected_row = None st.session_state["last_synced_abbr"] = None st.session_state["_clear_df_selection"] = True st.rerun() with tab_inspect: if not st.session_state.selected_row: st.warning("Select a material in All Materials first.") else: selected_abbr, selected_name = st.session_state.selected_row st.markdown(f"**Material:** {selected_name}") st.caption(selected_abbr) material_df = all_data[ (all_data["material_abbreviation"] == selected_abbr) & (all_data["value"].notna()) & (all_data["property_name"].notna()) ] section_options = sorted(material_df["section"].dropna().unique().tolist()) if not section_options: st.warning("No property data found for this material.") else: if st.session_state.inspect_section not in section_options: st.session_state.inspect_section = section_options[0] section_choice = st.selectbox( "Type of Property", section_options, index=section_options.index(st.session_state.inspect_section), key="inspect_section_select", ) st.session_state.inspect_section = section_choice properties_df = ( material_df[material_df["section"] == section_choice][ ["property_name", "section"] ] .drop_duplicates() .reset_index(drop=True) ) #st.dataframe(properties_df, use_container_width=True, hide_index=True, height=240) rows_html = "".join( f"{row['property_name']}" f"{row['section']}" for _, row in properties_df.iterrows() ) st.markdown(f"""
{rows_html}
PROPERTY NAME SECTION
""", unsafe_allow_html=True) property_options = properties_df["property_name"].dropna().tolist() if property_options: if st.session_state.inspect_property not in property_options: st.session_state.inspect_property = property_options[0] property_choice = st.selectbox( "Property", property_options, index=property_options.index(st.session_state.inspect_property), key="inspect_property_select", ) st.session_state.inspect_property = property_choice if st.button("Search", key="inspect_search", type="primary"): result = all_data[ (all_data["material_abbreviation"] == selected_abbr) & (all_data["property_name"] == property_choice) & (all_data["value"].notna()) ] if result.empty: st.warning("No data found for this material-property combination") else: st.subheader("Property Data") st.dataframe(result.T, use_container_width=True) st.subheader("Property Graph") image_path = Path("images") / f"{selected_abbr}_{property_choice}.png" if image_path.exists(): image = Image.open(image_path) st.image(image, use_container_width=True, caption="Stress strain curve") else: st.caption("No plot image available for this material-property pair.")