Source code for src.plots

import matplotlib.pyplot as plt
from matplotlib import cm, colors

import plotly.graph_objects as go                                              
import plotly.io as pio
import sys

if 'ipykernel' in sys.modules:
    pio.renderers.default = 'notebook'                                         # for jupyter notebooks
else:
    pio.renderers.default = 'browser'                                          # automatically opens the plot in a browser
                                           

import numpy as np
import data_output
import data_input


### (1) INTERACTIVE PLOTTING - Plotly
[docs] def plot_branch(direction:str) -> None: if data_input.df_supply_in.empty: print("Input DataFrame is empty. No data is available for plotting.") return if len(data_input.df_supply_in) < 2: raise ValueError("Dataframe must have at least 2 rows to plot line segments.") return lons_supply = data_input.df_supply_in["Longitude"].values lats_supply = data_input.df_supply_in["Latitude"].values lons_return = data_input.df_return_in["Longitude"].values lats_return = data_input.df_return_in["Latitude"].values # Trace for lines - supply line_trace_supply = go.Scattermapbox( lon = lons_supply, lat = lats_supply, mode = 'lines', line = dict(color = 'red', width = 7), name = 'Supply', showlegend = True ) # Trace for lines - return line_trace_return = go.Scattermapbox( lon = lons_return, lat = lats_return, mode = 'lines', line = dict(color = 'blue', width = 7), name = 'Return', showlegend = True ) # Trace for nodes - supply node_trace_supply = go.Scattermapbox( lon = lons_supply, lat = lats_supply, mode = 'markers', marker = dict(size = 15, color='red'), #name = 'Nodes - supply', showlegend = False ) # Trace for nodes - return node_trace_return = go.Scattermapbox( lon = lons_return, lat = lats_return, mode = 'markers', marker = dict(size = 15, color='blue'), #name = 'Nodes - return', showlegend = False ) # Centering the map centre_lat = np.mean(lats_supply) centre_lon = np.mean(lons_supply) # Selects the direction of the flow if direction.lower() == "supply": fig = go.Figure(data=[line_trace_supply, node_trace_supply]) map_title = "Configuration of the branch - supply" elif direction.lower() == "return": fig = go.Figure(data=[line_trace_return, node_trace_return]) map_title = "Configuration of the branch - return" elif direction.lower() == "all": fig = go.Figure(data=[line_trace_supply, line_trace_return, node_trace_supply, node_trace_return]) # Inserting data into the Figure map_title = "Configuration of the branch - supply and return" else: raise ValueError(f"Input ({direction}) not valid. Available options are 'supply', 'return', or 'all'.") fig.update_layout( mapbox = dict( style = 'open-street-map', # Selects Open Street Map application as the source for the background map center = dict(lat = centre_lat, lon = centre_lon), zoom = 15 ), margin = dict(l = 0, r = 0, t = 25, b = 0), title = map_title ) fig.show()
[docs] def plot_insulation(direction:str) -> None: if data_input.df_supply_in.empty: print("Input DataFrame is empty. No data is available for plotting.") return if direction.lower() == "supply": df = data_input.df_supply_in.copy() title_ins = "Original insulation thickness - supply" elif direction.lower() == "return": df = data_input.df_return_in.copy() title_ins = "Original insulation thickness - return" else: raise ValueError("Direction must be either 'supply' or 'return'.") return lons = df["Longitude"].values lats = df["Latitude"].values thickness = df["Insulation"].astype(float).values # Check values are in valid range - Must be in [0, 1] if not np.all((0 <= thickness) & (thickness <= 1)): raise ValueError("Insulation thickness values must be between 0 and 1.") # Get color from Turbo colormap cmap = cm.get_cmap('turbo') #cmap = cm.get_cmap('hot') hex_colors = [colors.to_hex(cmap(t)) for t in thickness] # Create line segments with manually assigned colors line_traces = [] for i in range(len(df) - 1): segment = go.Scattermapbox( lon = [lons[i], lons[i + 1]], lat = [lats[i], lats[i + 1]], mode = "lines", line = dict( color = hex_colors[i], width = 7 ), hovertemplate = f"Insulation: {thickness[i]:.2f}<extra></extra>", showlegend = False ) line_traces.append(segment) # Add black node markers node_trace = go.Scattermapbox( lon = lons, lat = lats, mode = 'markers', marker = dict(size = 8, color = 'black'), name= 'Nodes', showlegend = False, hoverinfo = 'skip' ) # Calculate map center (before using it in the colorbar trace) centre_lat = np.mean(lats) centre_lon = np.mean(lons) # Dummy trace for colorbar colorbar_trace = go.Scattermapbox( lon = [centre_lon], lat = [centre_lat], mode = 'markers', marker = dict( size = 0, opacity = 0, color = [0], colorscale = 'turbo', cmin = 0, cmax = 1, colorbar = dict( title = 'Thickness [%]', tickvals = [0, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1], ticktext = ["0", "10", "20", "30", "40", "50", "60", "70", "80", "90", "100"], len = 0.75 ) ), showlegend = False, hoverinfo = 'none' ) # Build and show figure fig = go.Figure(data = line_traces + [node_trace, colorbar_trace]) fig.update_layout( mapbox = dict( style = 'open-street-map', center = dict(lat = centre_lat, lon = centre_lon), zoom = 15 ), margin = dict(l = 0, r = 0, t = 25, b = 0), title = title_ins ) fig.show()
[docs] def plot_output_heatmap(direction:str, value_column:str) -> None: """ Plot line segments on a map using a single DataFrame that contains longitude, latitude, and a value column for coloring. Parameters ---------- direction : str Pipeline direction flow - 'supply' or 'return'. value_column : str Column in the dataframe to use for coloring the segments. Returns ------- None """ if direction.lower() == "supply": data_df = data_output.df_supply_out title_graph = value_column + " - supply" elif direction.lower() == "return": data_df = data_output.df_return_out title_graph = value_column + " - return" else: raise ValueError("Direction must be either 'supply' or 'return'.") if data_df.empty: print("Output DataFrame is empty. No data is available for plotting.") return if len(data_df) < 2: raise ValueError("Dataframe must have at least 2 rows to plot line segments.") return # Extract coordinates and values lons = data_df["Longitude"].values lats = data_df["Latitude"].values values = data_df[value_column].astype(float).values # Use values at the *end* of each segment for colouring #values_for_segments = values[1:] values_for_segments = values[0:] # Normalize for colormap vmin, vmax = values_for_segments.min(), values_for_segments.max() norm_values = (values_for_segments - vmin) / (vmax - vmin + 1e-12) # Get colours (colour scale) cmap = cm.get_cmap("turbo") #cmap = cm.get_cmap("hot") hex_colors = [colors.to_hex(cmap(v)) for v in norm_values] # Create line segments between consecutive points line_traces = [] for i in range(len(data_df) - 1): trace = go.Scattermapbox( lon = [lons[i], lons[i + 1]], lat = [lats[i], lats[i + 1]], mode = "lines", line = dict( color = hex_colors[i], width = 6 ), hovertemplate = f"{value_column}: {values_for_segments[i]:.2f}<extra></extra>", showlegend = False ) line_traces.append(trace) # Optional node markers node_trace = go.Scattermapbox( lon = lons, lat = lats, mode = 'markers', marker = dict(size = 7, color = 'black'), #name = 'Node', showlegend = False, hoverinfo = 'skip' ) # Dummy trace for colorbar colorbar_trace = go.Scattermapbox( lon = [None], lat = [None], mode = 'markers', marker = dict( size = 0.1, color = np.linspace(vmin, vmax, 100), colorscale = 'turbo', #colorscale = 'hot', cmin = vmin, cmax = vmax, colorbar = dict( title = value_column, len = 0.75 ) ), showlegend = False, hoverinfo = 'none' ) # Centre of the map centre_lat = np.mean(lats) centre_lon = np.mean(lons) # Build the figure fig = go.Figure(data = line_traces + [node_trace, colorbar_trace]) fig.update_layout( mapbox = dict( style = 'open-street-map', center = dict(lat = centre_lat, lon = centre_lon), zoom = 15 ), margin = dict(l = 0, r = 0, t = 25, b = 0), title = title_graph ) fig.show()
#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ### STATIC PLOTTING - matplotlib
[docs] def plot_temperature(direction:str) -> None: if direction.lower() == "supply": df_out = data_output.df_supply_out title = "Supply temperature" elif direction.lower() == "return": df_out = data_output.df_return_out title = "Return temperature" else: raise ValueError("Direction must be either 'supply' or 'return'.") if df_out.empty: print("Output DataFrame is empty. No data is available for plotting.") return plt.plot(df_out["L tot [m]"], df_out["T [°C]"], marker='.') plt.xlabel("L [m]") plt.ylabel("T [°C]") plt.title(title) plt.minorticks_on() plt.grid(which='major', linestyle='-', linewidth=0.75, alpha=0.7) plt.grid(which='minor', linestyle=':', linewidth=0.5, alpha=0.4) plt.tight_layout() plt.show()
[docs] def plot_heat_flow_loss(direction:str) -> None: if direction.lower() == "supply": df_out = data_output.df_supply_out title = "Heat flow loss - supply" elif direction.lower() == "return": df_out = data_output.df_return_out title = "Heat flow loss - return" else: raise ValueError("Direction must be either 'supply' or 'return'.") if df_out.empty: print("Output DataFrame is empty. No data is available for plotting.") return plt.plot(df_out["L tot [m]"], df_out["Qdot loss [W]"], marker='.') plt.xlabel("L [m]") plt.ylabel(r"$\dot{Q}_{LOSS}$ [W]") plt.title(title) plt.minorticks_on() plt.grid(which='major', linestyle='-', linewidth=0.75, alpha=0.7) plt.grid(which='minor', linestyle=':', linewidth=0.5, alpha=0.4) plt.tight_layout() plt.show()
[docs] def plot_normalised_heat_flow_loss(direction:str) -> None: if direction.lower() == "supply": df_out = data_output.df_supply_out title = "Normalised heat flow loss - supply" elif direction.lower() == "return": df_out = data_output.df_return_out title = "Normalised heat flow loss - return" else: raise ValueError("Direction must be either 'supply' or 'return'.") if df_out.empty: print("Output DataFrame is empty. No data is available for plotting.") return plt.plot(df_out["L tot [m]"], df_out["qdot loss [W/m]"], marker='.') plt.xlabel("L [m]") plt.ylabel(r"$\dot{q}_{LOSS}$ [W/m]") plt.title(title) plt.minorticks_on() plt.grid(which='major', linestyle='-', linewidth=0.75, alpha=0.7) plt.grid(which='minor', linestyle=':', linewidth=0.5, alpha=0.4) plt.tight_layout() plt.show()
[docs] def plot_total_heat_flow_loss(direction:str) -> None: if direction.lower() == "supply": df_out = data_output.df_supply_out title = "Total heat flow loss - supply" elif direction.lower() == "return": df_out = data_output.df_return_out title = "Total heat flow loss - return" else: raise ValueError("Direction must be either 'supply' or 'return'.") if df_out.empty: print("Output DataFrame is empty. No data is available for plotting.") return plt.plot(df_out["L tot [m]"], df_out["Qdot loss total [W]"], marker='.') plt.xlabel("L [m]") plt.ylabel(r"$\dot{q}_{LOSS}$ [W/m]") plt.title(title) plt.minorticks_on() plt.grid(which='major', linestyle='-', linewidth=0.75, alpha=0.7) plt.grid(which='minor', linestyle=':', linewidth=0.5, alpha=0.4) plt.tight_layout() plt.show()
[docs] def plot_mass_flow(direction:str) -> None: if direction.lower() == "supply": df_out = data_output.df_supply_out title = "Mass flow - supply" elif direction.lower() == "return": df_out = data_output.df_return_out title = "Mass flow - return" else: raise ValueError("Direction must be either 'supply' or 'return'.") if df_out.empty: print("Output DataFrame is empty. No data is available for plotting.") return plt.plot(df_out["L tot [m]"], df_out["mdot [kg/s]"], marker='.') plt.xlabel("L [m]") plt.ylabel(r"$\dot{q}_{LOSS}$ [W/m]") plt.title(title) plt.minorticks_on() plt.grid(which='major', linestyle='-', linewidth=0.75, alpha=0.7) plt.grid(which='minor', linestyle=':', linewidth=0.5, alpha=0.4) plt.tight_layout() plt.show()
[docs] def plot_heat_flow_loss_nodes(direction:str) -> None: if direction.lower() == "supply": df_out = data_output.df_supply_out title = "Heat flow loss (nodes) - supply" elif direction.lower() == "return": df_out = data_output.df_return_out title = "Heat flow loss (nodes) - return" else: raise ValueError("Direction must be either 'supply' or 'return'.") if df_out.empty: print("Output DataFrame is empty. No data is available for plotting.") return colorQ = df_out["Qdot loss [W]"] # # Create scatter plot # plt.figure(figsize=(8, 6)) scatter = plt.scatter( df_out["Longitude"], df_out["Latitude"], c = colorQ, cmap = plt.cm.get_cmap("turbo") #cmap=plt.cm.get_cmap("hot") ) # Add vertical colourbar cbar = plt.colorbar( scatter, orientation = "vertical", pad = 0.1, shrink = 0.8, format = "%.0f", label = r"$\dot{Q}_{LOSS}$ [W]" ) # # # Add label manually above the colorbar # # cbar.ax.text( # # 0.5, 1.05, # (x, y) position in axes coordinates # # r'$\dot{Q}_{\mathrm{LOSS}}$ [W]', # # ha='center', # # va='bottom', # # transform=cbar.ax.transAxes # # ) plt.xlabel("Longitude") plt.ylabel("Latitude") plt.title(title) plt.minorticks_on() plt.grid(which='major', linestyle='-', linewidth=0.75, alpha=0.7) plt.grid(which='minor', linestyle=':', linewidth=0.5, alpha=0.4) plt.tight_layout() plt.show()