Spaces:
Sleeping
Sleeping
| """ | |
| Hierarchical component visualization module for HVAC Load Calculator. | |
| This module provides visualization tools for building components. | |
| """ | |
| import streamlit as st | |
| import pandas as pd | |
| import numpy as np | |
| import plotly.graph_objects as go | |
| import plotly.express as px | |
| from typing import Dict, List, Any, Optional, Tuple | |
| import math | |
| # Import data models | |
| from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType | |
| class ComponentVisualization: | |
| """Class for hierarchical component visualization.""" | |
| def create_component_summary_table(components: Dict[str, List[Any]]) -> pd.DataFrame: | |
| """ | |
| Create a summary table of building components. | |
| Args: | |
| components: Dictionary with lists of building components | |
| Returns: | |
| DataFrame with component summary | |
| """ | |
| # Initialize data | |
| data = [] | |
| # Process walls | |
| for wall in components.get("walls", []): | |
| data.append({ | |
| "Component Type": "Wall", | |
| "Name": wall.name, | |
| "Orientation": wall.orientation.name, | |
| "Area (m²)": wall.area, | |
| "U-Value (W/m²·K)": wall.u_value, | |
| "Heat Transfer (W/K)": wall.area * wall.u_value | |
| }) | |
| # Process roofs | |
| for roof in components.get("roofs", []): | |
| data.append({ | |
| "Component Type": "Roof", | |
| "Name": roof.name, | |
| "Orientation": roof.orientation.name, | |
| "Area (m²)": roof.area, | |
| "U-Value (W/m²·K)": roof.u_value, | |
| "Heat Transfer (W/K)": roof.area * roof.u_value | |
| }) | |
| # Process floors | |
| for floor in components.get("floors", []): | |
| data.append({ | |
| "Component Type": "Floor", | |
| "Name": floor.name, | |
| "Orientation": "Horizontal", | |
| "Area (m²)": floor.area, | |
| "U-Value (W/m²·K)": floor.u_value, | |
| "Heat Transfer (W/K)": floor.area * floor.u_value | |
| }) | |
| # Process windows | |
| for window in components.get("windows", []): | |
| data.append({ | |
| "Component Type": "Window", | |
| "Name": window.name, | |
| "Orientation": window.orientation.name, | |
| "Area (m²)": window.area, | |
| "U-Value (W/m²·K)": window.u_value, | |
| "Heat Transfer (W/K)": window.area * window.u_value, | |
| "SHGC": window.shgc if hasattr(window, "shgc") else None | |
| }) | |
| # Process doors | |
| for door in components.get("doors", []): | |
| data.append({ | |
| "Component Type": "Door", | |
| "Name": door.name, | |
| "Orientation": door.orientation.name, | |
| "Area (m²)": door.area, | |
| "U-Value (W/m²·K)": door.u_value, | |
| "Heat Transfer (W/K)": door.area * door.u_value | |
| }) | |
| # Create DataFrame | |
| df = pd.DataFrame(data) | |
| return df | |
| def create_component_area_chart(components: Dict[str, List[Any]]) -> go.Figure: | |
| """ | |
| Create a pie chart of component areas. | |
| Args: | |
| components: Dictionary with lists of building components | |
| Returns: | |
| Plotly figure with component area breakdown | |
| """ | |
| # Calculate total areas by component type | |
| areas = { | |
| "Walls": sum(wall.area for wall in components.get("walls", [])), | |
| "Roofs": sum(roof.area for roof in components.get("roofs", [])), | |
| "Floors": sum(floor.area for floor in components.get("floors", [])), | |
| "Windows": sum(window.area for window in components.get("windows", [])), | |
| "Doors": sum(door.area for door in components.get("doors", [])) | |
| } | |
| # Create labels and values | |
| labels = list(areas.keys()) | |
| values = list(areas.values()) | |
| # Create pie chart | |
| fig = go.Figure(data=[go.Pie( | |
| labels=labels, | |
| values=values, | |
| hole=0.3, | |
| textinfo="label+percent", | |
| insidetextorientation="radial" | |
| )]) | |
| # Update layout | |
| fig.update_layout( | |
| title="Building Component Areas", | |
| height=500, | |
| legend=dict( | |
| orientation="h", | |
| yanchor="bottom", | |
| y=1.02, | |
| xanchor="right", | |
| x=1 | |
| ) | |
| ) | |
| return fig | |
| def create_orientation_area_chart(components: Dict[str, List[Any]]) -> go.Figure: | |
| """ | |
| Create a bar chart of areas by orientation. | |
| Args: | |
| components: Dictionary with lists of building components | |
| Returns: | |
| Plotly figure with area breakdown by orientation | |
| """ | |
| # Initialize areas by orientation | |
| orientation_areas = { | |
| "NORTH": 0, | |
| "NORTHEAST": 0, | |
| "EAST": 0, | |
| "SOUTHEAST": 0, | |
| "SOUTH": 0, | |
| "SOUTHWEST": 0, | |
| "WEST": 0, | |
| "NORTHWEST": 0, | |
| "HORIZONTAL": 0 | |
| } | |
| # Calculate areas by orientation for walls | |
| for wall in components.get("walls", []): | |
| orientation_areas[wall.orientation.name] += wall.area | |
| # Calculate areas by orientation for windows | |
| for window in components.get("windows", []): | |
| orientation_areas[window.orientation.name] += window.area | |
| # Calculate areas by orientation for doors | |
| for door in components.get("doors", []): | |
| orientation_areas[door.orientation.name] += door.area | |
| # Add roofs and floors to horizontal | |
| for roof in components.get("roofs", []): | |
| if roof.orientation.name == "HORIZONTAL": | |
| orientation_areas["HORIZONTAL"] += roof.area | |
| else: | |
| orientation_areas[roof.orientation.name] += roof.area | |
| for floor in components.get("floors", []): | |
| orientation_areas["HORIZONTAL"] += floor.area | |
| # Create labels and values | |
| orientations = [] | |
| areas = [] | |
| for orientation, area in orientation_areas.items(): | |
| if area > 0: | |
| orientations.append(orientation) | |
| areas.append(area) | |
| # Create bar chart | |
| fig = go.Figure(data=[go.Bar( | |
| x=orientations, | |
| y=areas, | |
| text=areas, | |
| texttemplate="%{y:.1f} m²", | |
| textposition="auto" | |
| )]) | |
| # Update layout | |
| fig.update_layout( | |
| title="Building Component Areas by Orientation", | |
| xaxis_title="Orientation", | |
| yaxis_title="Area (m²)", | |
| height=500 | |
| ) | |
| return fig | |
| def create_heat_transfer_chart(components: Dict[str, List[Any]]) -> go.Figure: | |
| """ | |
| Create a bar chart of heat transfer coefficients by component type. | |
| Args: | |
| components: Dictionary with lists of building components | |
| Returns: | |
| Plotly figure with heat transfer breakdown | |
| """ | |
| # Calculate heat transfer by component type | |
| heat_transfer = { | |
| "Walls": sum(wall.area * wall.u_value for wall in components.get("walls", [])), | |
| "Roofs": sum(roof.area * roof.u_value for roof in components.get("roofs", [])), | |
| "Floors": sum(floor.area * floor.u_value for floor in components.get("floors", [])), | |
| "Windows": sum(window.area * window.u_value for window in components.get("windows", [])), | |
| "Doors": sum(door.area * door.u_value for door in components.get("doors", [])) | |
| } | |
| # Create labels and values | |
| labels = list(heat_transfer.keys()) | |
| values = list(heat_transfer.values()) | |
| # Create bar chart | |
| fig = go.Figure(data=[go.Bar( | |
| x=labels, | |
| y=values, | |
| text=values, | |
| texttemplate="%{y:.1f} W/K", | |
| textposition="auto" | |
| )]) | |
| # Update layout | |
| fig.update_layout( | |
| title="Heat Transfer Coefficients by Component Type", | |
| xaxis_title="Component Type", | |
| yaxis_title="Heat Transfer Coefficient (W/K)", | |
| height=500 | |
| ) | |
| return fig | |
| def create_3d_building_model(components: Dict[str, List[Any]]) -> go.Figure: | |
| """ | |
| Create a 3D visualization of the building components. | |
| Args: | |
| components: Dictionary with lists of building components | |
| Returns: | |
| Plotly figure with 3D building model | |
| """ | |
| # Initialize figure | |
| fig = go.Figure() | |
| # Define colors | |
| colors = { | |
| "Wall": "lightblue", | |
| "Roof": "red", | |
| "Floor": "brown", | |
| "Window": "skyblue", | |
| "Door": "orange" | |
| } | |
| # Define orientation vectors | |
| orientation_vectors = { | |
| "NORTH": (0, 1, 0), | |
| "NORTHEAST": (0.7071, 0.7071, 0), | |
| "EAST": (1, 0, 0), | |
| "SOUTHEAST": (0.7071, -0.7071, 0), | |
| "SOUTH": (0, -1, 0), | |
| "SOUTHWEST": (-0.7071, -0.7071, 0), | |
| "WEST": (-1, 0, 0), | |
| "NORTHWEST": (-0.7071, 0.7071, 0), | |
| "HORIZONTAL": (0, 0, 1) | |
| } | |
| # Define building dimensions (simplified model) | |
| building_width = 10 | |
| building_depth = 10 | |
| building_height = 3 | |
| # Create walls | |
| for i, wall in enumerate(components.get("walls", [])): | |
| orientation = wall.orientation.name | |
| vector = orientation_vectors[orientation] | |
| # Determine wall position and dimensions | |
| if orientation in ["NORTH", "SOUTH"]: | |
| width = building_width | |
| height = building_height | |
| depth = 0.3 | |
| if orientation == "NORTH": | |
| x = 0 | |
| y = building_depth / 2 | |
| else: # SOUTH | |
| x = 0 | |
| y = -building_depth / 2 | |
| z = building_height / 2 | |
| elif orientation in ["EAST", "WEST"]: | |
| width = 0.3 | |
| height = building_height | |
| depth = building_depth | |
| if orientation == "EAST": | |
| x = building_width / 2 | |
| y = 0 | |
| else: # WEST | |
| x = -building_width / 2 | |
| y = 0 | |
| z = building_height / 2 | |
| else: # Diagonal orientations | |
| width = building_width / 2 | |
| height = building_height | |
| depth = 0.3 | |
| if orientation == "NORTHEAST": | |
| x = building_width / 4 | |
| y = building_depth / 4 | |
| elif orientation == "SOUTHEAST": | |
| x = building_width / 4 | |
| y = -building_depth / 4 | |
| elif orientation == "SOUTHWEST": | |
| x = -building_width / 4 | |
| y = -building_depth / 4 | |
| else: # NORTHWEST | |
| x = -building_width / 4 | |
| y = building_depth / 4 | |
| z = building_height / 2 | |
| # Add wall to figure | |
| fig.add_trace(go.Mesh3d( | |
| x=[x - width/2, x + width/2, x + width/2, x - width/2, x - width/2, x + width/2, x + width/2, x - width/2], | |
| y=[y - depth/2, y - depth/2, y + depth/2, y + depth/2, y - depth/2, y - depth/2, y + depth/2, y + depth/2], | |
| z=[z - height/2, z - height/2, z - height/2, z - height/2, z + height/2, z + height/2, z + height/2, z + height/2], | |
| i=[0, 0, 0, 1, 4, 4], | |
| j=[1, 2, 4, 2, 5, 6], | |
| k=[2, 3, 7, 3, 6, 7], | |
| color=colors["Wall"], | |
| opacity=0.7, | |
| name=f"Wall: {wall.name}" | |
| )) | |
| # Create roof | |
| for i, roof in enumerate(components.get("roofs", [])): | |
| # Add roof to figure | |
| fig.add_trace(go.Mesh3d( | |
| x=[-building_width/2, building_width/2, building_width/2, -building_width/2], | |
| y=[-building_depth/2, -building_depth/2, building_depth/2, building_depth/2], | |
| z=[building_height, building_height, building_height, building_height], | |
| i=[0], | |
| j=[1], | |
| k=[2], | |
| color=colors["Roof"], | |
| opacity=0.7, | |
| name=f"Roof: {roof.name}" | |
| )) | |
| fig.add_trace(go.Mesh3d( | |
| x=[-building_width/2, -building_width/2, building_width/2], | |
| y=[building_depth/2, -building_depth/2, -building_depth/2], | |
| z=[building_height, building_height, building_height], | |
| i=[0], | |
| j=[1], | |
| k=[2], | |
| color=colors["Roof"], | |
| opacity=0.7, | |
| name=f"Roof: {roof.name}" | |
| )) | |
| # Create floor | |
| for i, floor in enumerate(components.get("floors", [])): | |
| # Add floor to figure | |
| fig.add_trace(go.Mesh3d( | |
| x=[-building_width/2, building_width/2, building_width/2, -building_width/2], | |
| y=[-building_depth/2, -building_depth/2, building_depth/2, building_depth/2], | |
| z=[0, 0, 0, 0], | |
| i=[0], | |
| j=[1], | |
| k=[2], | |
| color=colors["Floor"], | |
| opacity=0.7, | |
| name=f"Floor: {floor.name}" | |
| )) | |
| fig.add_trace(go.Mesh3d( | |
| x=[-building_width/2, -building_width/2, building_width/2], | |
| y=[building_depth/2, -building_depth/2, -building_depth/2], | |
| z=[0, 0, 0], | |
| i=[0], | |
| j=[1], | |
| k=[2], | |
| color=colors["Floor"], | |
| opacity=0.7, | |
| name=f"Floor: {floor.name}" | |
| )) | |
| # Create windows | |
| for i, window in enumerate(components.get("windows", [])): | |
| orientation = window.orientation.name | |
| vector = orientation_vectors[orientation] | |
| # Determine window position and dimensions | |
| window_width = 1.5 | |
| window_height = 1.2 | |
| window_depth = 0.1 | |
| if orientation == "NORTH": | |
| x = i * 3 - building_width/4 | |
| y = building_depth / 2 | |
| z = building_height / 2 | |
| elif orientation == "SOUTH": | |
| x = i * 3 - building_width/4 | |
| y = -building_depth / 2 | |
| z = building_height / 2 | |
| elif orientation == "EAST": | |
| x = building_width / 2 | |
| y = i * 3 - building_depth/4 | |
| z = building_height / 2 | |
| elif orientation == "WEST": | |
| x = -building_width / 2 | |
| y = i * 3 - building_depth/4 | |
| z = building_height / 2 | |
| else: | |
| # Skip diagonal orientations for simplicity | |
| continue | |
| # Add window to figure | |
| fig.add_trace(go.Mesh3d( | |
| x=[x - window_width/2, x + window_width/2, x + window_width/2, x - window_width/2, x - window_width/2, x + window_width/2, x + window_width/2, x - window_width/2], | |
| y=[y - window_depth/2, y - window_depth/2, y + window_depth/2, y + window_depth/2, y - window_depth/2, y - window_depth/2, y + window_depth/2, y + window_depth/2], | |
| z=[z - window_height/2, z - window_height/2, z - window_height/2, z - window_height/2, z + window_height/2, z + window_height/2, z + window_height/2, z + window_height/2], | |
| i=[0, 0, 0, 1, 4, 4], | |
| j=[1, 2, 4, 2, 5, 6], | |
| k=[2, 3, 7, 3, 6, 7], | |
| color=colors["Window"], | |
| opacity=0.5, | |
| name=f"Window: {window.name}" | |
| )) | |
| # Create doors | |
| for i, door in enumerate(components.get("doors", [])): | |
| orientation = door.orientation.name | |
| vector = orientation_vectors[orientation] | |
| # Determine door position and dimensions | |
| door_width = 1.0 | |
| door_height = 2.0 | |
| door_depth = 0.1 | |
| if orientation == "NORTH": | |
| x = i * 3 | |
| y = building_depth / 2 | |
| z = door_height / 2 | |
| elif orientation == "SOUTH": | |
| x = i * 3 | |
| y = -building_depth / 2 | |
| z = door_height / 2 | |
| elif orientation == "EAST": | |
| x = building_width / 2 | |
| y = i * 3 | |
| z = door_height / 2 | |
| elif orientation == "WEST": | |
| x = -building_width / 2 | |
| y = i * 3 | |
| z = door_height / 2 | |
| else: | |
| # Skip diagonal orientations for simplicity | |
| continue | |
| # Add door to figure | |
| fig.add_trace(go.Mesh3d( | |
| x=[x - door_width/2, x + door_width/2, x + door_width/2, x - door_width/2, x - door_width/2, x + door_width/2, x + door_width/2, x - door_width/2], | |
| y=[y - door_depth/2, y - door_depth/2, y + door_depth/2, y + door_depth/2, y - door_depth/2, y - door_depth/2, y + door_depth/2, y + door_depth/2], | |
| z=[z - door_height/2, z - door_height/2, z - door_height/2, z - door_height/2, z + door_height/2, z + door_height/2, z + door_height/2, z + door_height/2], | |
| i=[0, 0, 0, 1, 4, 4], | |
| j=[1, 2, 4, 2, 5, 6], | |
| k=[2, 3, 7, 3, 6, 7], | |
| color=colors["Door"], | |
| opacity=0.7, | |
| name=f"Door: {door.name}" | |
| )) | |
| # Update layout | |
| fig.update_layout( | |
| title="3D Building Model", | |
| scene=dict( | |
| xaxis_title="X", | |
| yaxis_title="Y", | |
| zaxis_title="Z", | |
| aspectmode="data" | |
| ), | |
| height=700, | |
| margin=dict(l=0, r=0, b=0, t=30) | |
| ) | |
| return fig | |
| def display_component_visualization(components: Dict[str, List[Any]]) -> None: | |
| """ | |
| Display component visualization in Streamlit. | |
| Args: | |
| components: Dictionary with lists of building components | |
| """ | |
| st.header("Building Component Visualization") | |
| # Create tabs for different visualizations | |
| tab1, tab2, tab3, tab4, tab5 = st.tabs([ | |
| "Component Summary", | |
| "Area Breakdown", | |
| "Orientation Analysis", | |
| "Heat Transfer Analysis", | |
| "3D Building Model" | |
| ]) | |
| with tab1: | |
| st.subheader("Component Summary") | |
| df = ComponentVisualization.create_component_summary_table(components) | |
| st.dataframe(df, use_container_width=True) | |
| # Add download button for CSV | |
| csv = df.to_csv(index=False).encode('utf-8') | |
| st.download_button( | |
| label="Download Component Summary as CSV", | |
| data=csv, | |
| file_name="component_summary.csv", | |
| mime="text/csv" | |
| ) | |
| with tab2: | |
| st.subheader("Area Breakdown") | |
| fig = ComponentVisualization.create_component_area_chart(components) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with tab3: | |
| st.subheader("Orientation Analysis") | |
| fig = ComponentVisualization.create_orientation_area_chart(components) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with tab4: | |
| st.subheader("Heat Transfer Analysis") | |
| fig = ComponentVisualization.create_heat_transfer_chart(components) | |
| st.plotly_chart(fig, use_container_width=True) | |
| with tab5: | |
| st.subheader("3D Building Model") | |
| fig = ComponentVisualization.create_3d_building_model(components) | |
| st.plotly_chart(fig, use_container_width=True) | |
| # Create a singleton instance | |
| component_visualization = ComponentVisualization() | |
| # Example usage | |
| if __name__ == "__main__": | |
| import streamlit as st | |
| from data.building_components import Wall, Roof, Floor, Window, Door, Orientation, ComponentType | |
| # Create sample building components | |
| walls = [ | |
| Wall( | |
| id="wall1", | |
| name="North Wall", | |
| component_type=ComponentType.WALL, | |
| u_value=0.5, | |
| area=20.0, | |
| orientation=Orientation.NORTH, | |
| wall_type="Brick", | |
| wall_group="B" | |
| ), | |
| Wall( | |
| id="wall2", | |
| name="South Wall", | |
| component_type=ComponentType.WALL, | |
| u_value=0.5, | |
| area=20.0, | |
| orientation=Orientation.SOUTH, | |
| wall_type="Brick", | |
| wall_group="B" | |
| ), | |
| Wall( | |
| id="wall3", | |
| name="East Wall", | |
| component_type=ComponentType.WALL, | |
| u_value=0.5, | |
| area=15.0, | |
| orientation=Orientation.EAST, | |
| wall_type="Brick", | |
| wall_group="B" | |
| ), | |
| Wall( | |
| id="wall4", | |
| name="West Wall", | |
| component_type=ComponentType.WALL, | |
| u_value=0.5, | |
| area=15.0, | |
| orientation=Orientation.WEST, | |
| wall_type="Brick", | |
| wall_group="B" | |
| ) | |
| ] | |
| roofs = [ | |
| Roof( | |
| id="roof1", | |
| name="Flat Roof", | |
| component_type=ComponentType.ROOF, | |
| u_value=0.3, | |
| area=100.0, | |
| orientation=Orientation.HORIZONTAL, | |
| roof_type="Concrete", | |
| roof_group="C" | |
| ) | |
| ] | |
| floors = [ | |
| Floor( | |
| id="floor1", | |
| name="Ground Floor", | |
| component_type=ComponentType.FLOOR, | |
| u_value=0.4, | |
| area=100.0, | |
| floor_type="Concrete" | |
| ) | |
| ] | |
| windows = [ | |
| Window( | |
| id="window1", | |
| name="North Window 1", | |
| component_type=ComponentType.WINDOW, | |
| u_value=2.8, | |
| area=4.0, | |
| orientation=Orientation.NORTH, | |
| shgc=0.7, | |
| vt=0.8, | |
| window_type="Double Glazed", | |
| glazing_layers=2, | |
| gas_fill="Air", | |
| low_e_coating=False | |
| ), | |
| Window( | |
| id="window2", | |
| name="South Window 1", | |
| component_type=ComponentType.WINDOW, | |
| u_value=2.8, | |
| area=6.0, | |
| orientation=Orientation.SOUTH, | |
| shgc=0.7, | |
| vt=0.8, | |
| window_type="Double Glazed", | |
| glazing_layers=2, | |
| gas_fill="Air", | |
| low_e_coating=False | |
| ), | |
| Window( | |
| id="window3", | |
| name="East Window 1", | |
| component_type=ComponentType.WINDOW, | |
| u_value=2.8, | |
| area=3.0, | |
| orientation=Orientation.EAST, | |
| shgc=0.7, | |
| vt=0.8, | |
| window_type="Double Glazed", | |
| glazing_layers=2, | |
| gas_fill="Air", | |
| low_e_coating=False | |
| ) | |
| ] | |
| doors = [ | |
| Door( | |
| id="door1", | |
| name="Front Door", | |
| component_type=ComponentType.DOOR, | |
| u_value=2.0, | |
| area=2.0, | |
| orientation=Orientation.SOUTH, | |
| door_type="Solid Wood" | |
| ) | |
| ] | |
| # Create components dictionary | |
| components = { | |
| "walls": walls, | |
| "roofs": roofs, | |
| "floors": floors, | |
| "windows": windows, | |
| "doors": doors | |
| } | |
| # Display component visualization | |
| component_visualization.display_component_visualization(components) | |