|
|
""" |
|
|
Visualization utilities for HVAC Load Calculator |
|
|
|
|
|
This module provides enhanced visualization functions for creating interactive |
|
|
and informative charts for the HVAC Load Calculator application. |
|
|
""" |
|
|
|
|
|
import plotly.express as px |
|
|
import plotly.graph_objects as go |
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
|
|
|
|
|
|
def create_load_breakdown_chart(load_components, title="Load Breakdown"): |
|
|
""" |
|
|
Create an enhanced pie chart for load components breakdown. |
|
|
|
|
|
Args: |
|
|
load_components (dict): Dictionary of load components and their values |
|
|
title (str): Chart title |
|
|
|
|
|
Returns: |
|
|
plotly.graph_objects.Figure: Interactive pie chart |
|
|
""" |
|
|
|
|
|
load_components = {k: v for k, v in load_components.items() if v > 0} |
|
|
|
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
fig.add_trace(go.Pie( |
|
|
labels=list(load_components.keys()), |
|
|
values=list(load_components.values()), |
|
|
textinfo='label+percent', |
|
|
insidetextorientation='radial', |
|
|
marker=dict( |
|
|
colors=px.colors.qualitative.Bold, |
|
|
line=dict(color='white', width=2) |
|
|
), |
|
|
pull=[0.05 if x == max(load_components.values()) else 0 for x in load_components.values()], |
|
|
hovertemplate='<b>%{label}</b><br>%{value:.1f} W<br>%{percent}<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
title={ |
|
|
'text': title, |
|
|
'y': 0.95, |
|
|
'x': 0.5, |
|
|
'xanchor': 'center', |
|
|
'yanchor': 'top', |
|
|
'font': dict(size=20) |
|
|
}, |
|
|
legend=dict( |
|
|
orientation="h", |
|
|
yanchor="bottom", |
|
|
y=-0.2, |
|
|
xanchor="center", |
|
|
x=0.5, |
|
|
font=dict(size=12) |
|
|
), |
|
|
height=500, |
|
|
margin=dict(t=80, b=80, l=40, r=40), |
|
|
paper_bgcolor='rgba(0,0,0,0)', |
|
|
plot_bgcolor='rgba(0,0,0,0)' |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_component_bar_chart(df, x_col, y_col, color_col=None, title="Component Breakdown"): |
|
|
""" |
|
|
Create an enhanced bar chart for component breakdown. |
|
|
|
|
|
Args: |
|
|
df (pd.DataFrame): DataFrame containing the data |
|
|
x_col (str): Column name for x-axis |
|
|
y_col (str): Column name for y-axis |
|
|
color_col (str, optional): Column name for color grouping |
|
|
title (str): Chart title |
|
|
|
|
|
Returns: |
|
|
plotly.graph_objects.Figure: Interactive bar chart |
|
|
""" |
|
|
|
|
|
if color_col: |
|
|
fig = px.bar( |
|
|
df, |
|
|
x=x_col, |
|
|
y=y_col, |
|
|
color=color_col, |
|
|
title=title, |
|
|
color_discrete_sequence=px.colors.qualitative.Bold, |
|
|
height=500, |
|
|
text=y_col |
|
|
) |
|
|
else: |
|
|
fig = px.bar( |
|
|
df, |
|
|
x=x_col, |
|
|
y=y_col, |
|
|
title=title, |
|
|
color_discrete_sequence=px.colors.qualitative.Bold, |
|
|
height=500, |
|
|
text=y_col |
|
|
) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
xaxis_title=x_col, |
|
|
yaxis_title=y_col, |
|
|
legend_title=color_col if color_col else "", |
|
|
font=dict(size=12), |
|
|
xaxis={'categoryorder': 'total descending'}, |
|
|
paper_bgcolor='rgba(0,0,0,0)', |
|
|
plot_bgcolor='rgba(0,0,0,0)', |
|
|
hovermode="x unified" |
|
|
) |
|
|
|
|
|
|
|
|
fig.update_traces( |
|
|
texttemplate='%{y:.1f}', |
|
|
textposition='outside', |
|
|
hovertemplate='<b>%{x}</b><br>%{y:.1f}<extra></extra>' |
|
|
) |
|
|
|
|
|
|
|
|
fig.update_yaxes( |
|
|
showgrid=True, |
|
|
gridwidth=1, |
|
|
gridcolor='rgba(211,211,211,0.3)' |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_stacked_bar_chart(df, x_col, y_cols, names, title="Stacked Bar Chart"): |
|
|
""" |
|
|
Create an enhanced stacked bar chart. |
|
|
|
|
|
Args: |
|
|
df (pd.DataFrame): DataFrame containing the data |
|
|
x_col (str): Column name for x-axis |
|
|
y_cols (list): List of column names for y-axis values |
|
|
names (list): List of names for each y-column |
|
|
title (str): Chart title |
|
|
|
|
|
Returns: |
|
|
plotly.graph_objects.Figure: Interactive stacked bar chart |
|
|
""" |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
for i, y_col in enumerate(y_cols): |
|
|
fig.add_trace(go.Bar( |
|
|
x=df[x_col], |
|
|
y=df[y_col], |
|
|
name=names[i], |
|
|
hovertemplate=f'<b>{names[i]}</b>: %{{y:.1f}}<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
title=title, |
|
|
xaxis_title=x_col, |
|
|
yaxis_title="Value", |
|
|
barmode='stack', |
|
|
height=500, |
|
|
legend=dict( |
|
|
orientation="h", |
|
|
yanchor="bottom", |
|
|
y=1.02, |
|
|
xanchor="center", |
|
|
x=0.5 |
|
|
), |
|
|
paper_bgcolor='rgba(0,0,0,0)', |
|
|
plot_bgcolor='rgba(0,0,0,0)', |
|
|
hovermode="x unified" |
|
|
) |
|
|
|
|
|
|
|
|
fig.update_yaxes( |
|
|
showgrid=True, |
|
|
gridwidth=1, |
|
|
gridcolor='rgba(211,211,211,0.3)' |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_grouped_bar_chart(df, x_col, y_cols, names, title="Grouped Bar Chart"): |
|
|
""" |
|
|
Create an enhanced grouped bar chart. |
|
|
|
|
|
Args: |
|
|
df (pd.DataFrame): DataFrame containing the data |
|
|
x_col (str): Column name for x-axis |
|
|
y_cols (list): List of column names for y-axis values |
|
|
names (list): List of names for each y-column |
|
|
title (str): Chart title |
|
|
|
|
|
Returns: |
|
|
plotly.graph_objects.Figure: Interactive grouped bar chart |
|
|
""" |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
for i, y_col in enumerate(y_cols): |
|
|
fig.add_trace(go.Bar( |
|
|
x=df[x_col], |
|
|
y=df[y_col], |
|
|
name=names[i], |
|
|
hovertemplate=f'<b>{names[i]}</b>: %{{y:.1f}}<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
title=title, |
|
|
xaxis_title=x_col, |
|
|
yaxis_title="Value", |
|
|
barmode='group', |
|
|
height=500, |
|
|
legend=dict( |
|
|
orientation="h", |
|
|
yanchor="bottom", |
|
|
y=1.02, |
|
|
xanchor="center", |
|
|
x=0.5 |
|
|
), |
|
|
paper_bgcolor='rgba(0,0,0,0)', |
|
|
plot_bgcolor='rgba(0,0,0,0)', |
|
|
hovermode="x unified" |
|
|
) |
|
|
|
|
|
|
|
|
fig.update_yaxes( |
|
|
showgrid=True, |
|
|
gridwidth=1, |
|
|
gridcolor='rgba(211,211,211,0.3)' |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_heat_map_chart(df, x_col, y_col, z_col, title="Heat Map"): |
|
|
""" |
|
|
Create an enhanced heat map chart. |
|
|
|
|
|
Args: |
|
|
df (pd.DataFrame): DataFrame containing the data |
|
|
x_col (str): Column name for x-axis |
|
|
y_col (str): Column name for y-axis |
|
|
z_col (str): Column name for z-axis (color) |
|
|
title (str): Chart title |
|
|
|
|
|
Returns: |
|
|
plotly.graph_objects.Figure: Interactive heat map chart |
|
|
""" |
|
|
|
|
|
fig = px.density_heatmap( |
|
|
df, |
|
|
x=x_col, |
|
|
y=y_col, |
|
|
z=z_col, |
|
|
title=title, |
|
|
color_continuous_scale="Viridis", |
|
|
height=500 |
|
|
) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
xaxis_title=x_col, |
|
|
yaxis_title=y_col, |
|
|
font=dict(size=12), |
|
|
paper_bgcolor='rgba(0,0,0,0)', |
|
|
plot_bgcolor='rgba(0,0,0,0)' |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_line_chart(df, x_col, y_cols, names, title="Line Chart"): |
|
|
""" |
|
|
Create an enhanced line chart. |
|
|
|
|
|
Args: |
|
|
df (pd.DataFrame): DataFrame containing the data |
|
|
x_col (str): Column name for x-axis |
|
|
y_cols (list): List of column names for y-axis values |
|
|
names (list): List of names for each y-column |
|
|
title (str): Chart title |
|
|
|
|
|
Returns: |
|
|
plotly.graph_objects.Figure: Interactive line chart |
|
|
""" |
|
|
|
|
|
fig = go.Figure() |
|
|
|
|
|
|
|
|
for i, y_col in enumerate(y_cols): |
|
|
fig.add_trace(go.Scatter( |
|
|
x=df[x_col], |
|
|
y=df[y_col], |
|
|
mode='lines+markers', |
|
|
name=names[i], |
|
|
hovertemplate=f'<b>{names[i]}</b>: %{{y:.1f}}<extra></extra>' |
|
|
)) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
title=title, |
|
|
xaxis_title=x_col, |
|
|
yaxis_title="Value", |
|
|
height=500, |
|
|
legend=dict( |
|
|
orientation="h", |
|
|
yanchor="bottom", |
|
|
y=1.02, |
|
|
xanchor="center", |
|
|
x=0.5 |
|
|
), |
|
|
paper_bgcolor='rgba(0,0,0,0)', |
|
|
plot_bgcolor='rgba(0,0,0,0)', |
|
|
hovermode="x unified" |
|
|
) |
|
|
|
|
|
|
|
|
fig.update_yaxes( |
|
|
showgrid=True, |
|
|
gridwidth=1, |
|
|
gridcolor='rgba(211,211,211,0.3)' |
|
|
) |
|
|
|
|
|
fig.update_xaxes( |
|
|
showgrid=True, |
|
|
gridwidth=1, |
|
|
gridcolor='rgba(211,211,211,0.3)' |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_sankey_diagram(nodes, links, title="Energy Flow"): |
|
|
""" |
|
|
Create a Sankey diagram for energy flow visualization. |
|
|
|
|
|
Args: |
|
|
nodes (list): List of node labels |
|
|
links (dict): Dictionary with source, target, and value lists |
|
|
title (str): Chart title |
|
|
|
|
|
Returns: |
|
|
plotly.graph_objects.Figure: Interactive Sankey diagram |
|
|
""" |
|
|
|
|
|
fig = go.Figure(data=[go.Sankey( |
|
|
node=dict( |
|
|
pad=15, |
|
|
thickness=20, |
|
|
line=dict(color="black", width=0.5), |
|
|
label=nodes, |
|
|
color="blue" |
|
|
), |
|
|
link=dict( |
|
|
source=links['source'], |
|
|
target=links['target'], |
|
|
value=links['value'], |
|
|
hovertemplate='%{source.label} → %{target.label}: %{value:.1f} W<extra></extra>' |
|
|
) |
|
|
)]) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
title=title, |
|
|
font=dict(size=12), |
|
|
height=600, |
|
|
paper_bgcolor='rgba(0,0,0,0)', |
|
|
plot_bgcolor='rgba(0,0,0,0)' |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_gauge_chart(value, min_val, max_val, title="Gauge", threshold_values=None, threshold_colors=None): |
|
|
""" |
|
|
Create a gauge chart for displaying a value within a range. |
|
|
|
|
|
Args: |
|
|
value (float): Value to display |
|
|
min_val (float): Minimum value of the range |
|
|
max_val (float): Maximum value of the range |
|
|
title (str): Chart title |
|
|
threshold_values (list, optional): List of threshold values |
|
|
threshold_colors (list, optional): List of colors for each threshold |
|
|
|
|
|
Returns: |
|
|
plotly.graph_objects.Figure: Interactive gauge chart |
|
|
""" |
|
|
|
|
|
if threshold_values is None: |
|
|
threshold_values = [min_val, (min_val + max_val) / 2, max_val] |
|
|
|
|
|
if threshold_colors is None: |
|
|
threshold_colors = ["green", "yellow", "red"] |
|
|
|
|
|
|
|
|
fig = go.Figure(go.Indicator( |
|
|
mode="gauge+number", |
|
|
value=value, |
|
|
domain={'x': [0, 1], 'y': [0, 1]}, |
|
|
title={'text': title}, |
|
|
gauge={ |
|
|
'axis': {'range': [min_val, max_val]}, |
|
|
'bar': {'color': "darkblue"}, |
|
|
'steps': [ |
|
|
{'range': [threshold_values[i], threshold_values[i+1]], 'color': threshold_colors[i]} |
|
|
for i in range(len(threshold_values)-1) |
|
|
], |
|
|
'threshold': { |
|
|
'line': {'color': "red", 'width': 4}, |
|
|
'thickness': 0.75, |
|
|
'value': value |
|
|
} |
|
|
} |
|
|
)) |
|
|
|
|
|
|
|
|
fig.update_layout( |
|
|
height=300, |
|
|
paper_bgcolor='rgba(0,0,0,0)', |
|
|
plot_bgcolor='rgba(0,0,0,0)' |
|
|
) |
|
|
|
|
|
return fig |
|
|
|
|
|
|
|
|
def create_enhanced_results_visualization(results): |
|
|
""" |
|
|
Create enhanced visualizations for HVAC load calculation results. |
|
|
|
|
|
Args: |
|
|
results (dict): Dictionary containing calculation results |
|
|
|
|
|
Returns: |
|
|
dict: Dictionary of plotly figures |
|
|
""" |
|
|
figures = {} |
|
|
|
|
|
|
|
|
load_components = { |
|
|
'Walls': results.get('wall_loss', 0), |
|
|
'Roof': results.get('roof_loss', 0), |
|
|
'Floor': results.get('floor_loss', 0), |
|
|
'Windows & Doors': results.get('window_loss', 0), |
|
|
'Infiltration': results.get('infiltration_loss', 0), |
|
|
'Ventilation': results.get('ventilation_loss', 0) - results.get('infiltration_loss', 0) |
|
|
} |
|
|
|
|
|
|
|
|
figures['load_breakdown'] = create_load_breakdown_chart( |
|
|
load_components, |
|
|
title="Heating Load Components" |
|
|
) |
|
|
|
|
|
|
|
|
if 'internal_gain' in results and results['internal_gain'] > 0: |
|
|
|
|
|
nodes = [ |
|
|
"Walls", "Roof", "Floor", "Windows & Doors", |
|
|
"Infiltration", "Ventilation", "Internal Gains", |
|
|
"Total Heat Loss", "Net Heating Load" |
|
|
] |
|
|
|
|
|
links = { |
|
|
'source': [0, 1, 2, 3, 4, 5, 7, 6], |
|
|
'target': [7, 7, 7, 7, 7, 7, 8, 8], |
|
|
'value': [ |
|
|
load_components['Walls'], |
|
|
load_components['Roof'], |
|
|
load_components['Floor'], |
|
|
load_components['Windows & Doors'], |
|
|
load_components['Infiltration'], |
|
|
load_components['Ventilation'], |
|
|
results['internal_gain'], |
|
|
results['total_heat_loss'] |
|
|
] |
|
|
} |
|
|
|
|
|
figures['energy_flow'] = create_sankey_diagram( |
|
|
nodes, |
|
|
links, |
|
|
title="Heating Energy Flow" |
|
|
) |
|
|
|
|
|
|
|
|
if 'net_heating_load' in results and 'building_info' in results: |
|
|
floor_area = results['building_info'].get('floor_area', 80.0) |
|
|
heating_load_per_area = results['net_heating_load'] / floor_area |
|
|
|
|
|
figures['load_per_area_gauge'] = create_gauge_chart( |
|
|
heating_load_per_area, |
|
|
0, |
|
|
200, |
|
|
title="Heating Load per Area (W/m²)", |
|
|
threshold_values=[0, 50, 100, 150, 200], |
|
|
threshold_colors=["green", "lightgreen", "yellow", "orange", "red"] |
|
|
) |
|
|
|
|
|
return figures |
|
|
|