HVAC-03 / utils /component_visualization.py
mabuseif's picture
Upload 27 files
845939b verified
"""
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."""
@staticmethod
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
@staticmethod
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
@staticmethod
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
@staticmethod
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
@staticmethod
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
@staticmethod
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)