Day 21 – Advanced Data Visualisation with Plotly & 3D Graphics

 

📊 Day 21 – Advanced Data Visualisation with Plotly & 3D Graphics

1. Learning Objectives

By the end of Day 21, you will be able to:

  • Create interactive 2D plots (scatter, line, bar, heatmap) using plotly.express.
  • Build 3D visualisations of structural frames, column grids, and building massing models.
  • Generate heatmaps for energy performance or thermal comfort analysis.
  • Create interactive dashboards for design review using plotly subplots and hover information.
  • Export interactive HTML visualisations to share with clients and colleagues.

2. Concept Explanation

2.1 Why Advanced Visualisation in AEC?

Static plots (matplotlib) are useful reports, but interactive visualisations let stakeholders explore data themselves:

  • Rotate a 3D structural frame to inspect connections.
  • Hover over a beam to see its span, load, and utilisation.
  • Filter a heatmap by floor or zone.
  • Share an HTML file that anyone can open in a browser – no Python required.

plotly is the leading library for interactive visualisation in Python. It integrates with pandas and numpy seamlessly.

2.2 Installation

pip install plotly pandas numpy

2.3 plotly.express Basics

plotly.express (px) provides high‑level functions for common chart types:

import plotly.express as px
import pandas as pd

# Sample data
df = pd.DataFrame({
    "Beam": ["B1", "B2", "B3", "B4", "B5"],
    "Span (m)": [6.0, 4.5, 7.2, 5.0, 8.1],
    "Load (kN/m)": [25, 18, 30, 22, 35],
    "Moment (kNm)": [112.5, 45.6, 194.4, 68.8, 286.7]
})

# Interactive bar chart
fig = px.bar(df, x="Beam", y="Moment (kNm)", title="Bending Moments by Beam",
             hover_data=["Span (m)", "Load (kN/m)"])
fig.show()  # Opens in browser
# fig.write_html("beam_moments.html")  # Save as standalone HTML

2.4 3D Scatter & Line Plots

# 3D column grid
import numpy as np
x = np.arange(0, 4) * 6.0
y = np.arange(0, 3) * 8.0
z = np.zeros(12)  # ground level for all columns

df_grid = pd.DataFrame({"X": np.tile(x, 3), "Y": np.repeat(y, 4), "Z": z, 
                        "Column": [f"C{i+1}" for i in range(12)]})

fig = px.scatter_3d(df_grid, x="X", y="Y", z="Z", text="Column",
                    title="3D Column Grid", width=800, height=600)
fig.update_traces(marker_size=8)
fig.show()

2.5 Heatmaps for Thermal or Structural Data

# Simulated U-values across a floor plate (W/m²K)
z_data = np.random.uniform(0.2, 1.5, size=(5, 6))  # 5 rows, 6 columns
x_labels = [f"Grid {i+1}" for i in range(6)]
y_labels = [f"Row {i+1}" for i in range(5)]

fig = px.imshow(z_data, x=x_labels, y=y_labels, 
                title="U-Value Distribution Across Floor Plate (W/m²K)",
                color_continuous_scale="RdYlGn_r",
                aspect="auto")
fig.update_xaxes(title="Column Grid")
fig.update_yaxes(title="Row Grid")
fig.show()

3. Code Examples

Example 1: Interactive beam utilisation chart

import plotly.express as px
import pandas as pd

# Sample beam data
beams = [
    {"Mark": "B1", "Span": 6.0, "Load": 25, "Moment": 112.5, "Capacity": 150, "Utilisation": 75},
    {"Mark": "B2", "Span": 4.5, "Load": 18, "Moment": 45.6, "Capacity": 120, "Utilisation": 38},
    {"Mark": "B3", "Span": 7.2, "Load": 30, "Moment": 194.4, "Capacity": 200, "Utilisation": 97},
    {"Mark": "B4", "Span": 5.0, "Load": 22, "Moment": 68.8, "Capacity": 130, "Utilisation": 53},
    {"Mark": "B5", "Span": 8.0, "Load": 35, "Moment": 286.7, "Capacity": 300, "Utilisation": 96},
]

df = pd.DataFrame(beams)

# Create interactive bar chart with colour coding
fig = px.bar(df, x="Mark", y="Utilisation", 
             title="Beam Utilisation (%) – Target < 100%",
             color="Utilisation",
             color_continuous_scale=["green", "yellow", "red"],
             range_color=[0, 100],
             hover_data=["Span", "Load", "Moment", "Capacity"])

fig.add_hline(y=100, line_dash="dash", line_color="red", 
              annotation_text="Capacity limit")
fig.update_layout(yaxis_range=[0, 120])
fig.show()

Example 3: 3D structural frame visualisation

import plotly.graph_objects as go
import numpy as np
import pandas as pd

# Define a 2-bay, 2-storey frame
bays_x = 2
bays_y = 2
storeys = 2
bay_width = 6.0
bay_depth = 8.0
storey_height = 3.5

# Generate node coordinates
nodes = []
for level in range(storeys + 1):
    z = level * storey_height
    for row in range(bays_y + 1):
        y = row * bay_depth
        for col in range(bays_x + 1):
            x = col * bay_width
            nodes.append({"X": x, "Y": y, "Z": z, "Node": len(nodes)+1})

df_nodes = pd.DataFrame(nodes)

# Create members (columns and beams)
members = []
# Columns: connect nodes at same (x,y) across levels
for level in range(storeys):
    for row in range(bays_y + 1):
        for col in range(bays_x + 1):
            n1 = level * ((bays_x+1)*(bays_y+1)) + row*(bays_x+1) + col
            n2 = (level+1) * ((bays_x+1)*(bays_y+1)) + row*(bays_x+1) + col
            members.append((n1, n2, "column"))
# Beams in X direction (at each level, each row)
for level in range(storeys + 1):
    for row in range(bays_y + 1):
        for col in range(bays_x):
            n1 = level * ((bays_x+1)*(bays_y+1)) + row*(bays_x+1) + col
            n2 = level * ((bays_x+1)*(bays_y+1)) + row*(bays_x+1) + col + 1
            members.append((n1, n2, "beam_x"))
# Beams in Y direction (at each level, each column)
for level in range(storeys + 1):
    for col in range(bays_x + 1):
        for row in range(bays_y):
            n1 = level * ((bays_x+1)*(bays_y+1)) + row*(bays_x+1) + col
            n2 = level * ((bays_x+1)*(bays_y+1)) + (row+1)*(bays_x+1) + col
            members.append((n1, n2, "beam_y"))

# Build plotly traces
fig = go.Figure()

# Add nodes
fig.add_trace(go.Scatter3d(
    x=df_nodes["X"], y=df_nodes["Y"], z=df_nodes["Z"],
    mode='markers+text',
    marker=dict(size=4, color='blue'),
    text=df_nodes["Node"],
    textposition="top center",
    name="Nodes"
))

# Add members
for (i, j, mtype) in members:
    color = "gray" if mtype == "column" else ("orange" if mtype == "beam_x" else "green")
    width = 8 if mtype == "column" else 6
    fig.add_trace(go.Scatter3d(
        x=[nodes[i]["X"], nodes[j]["X"]],
        y=[nodes[i]["Y"], nodes[j]["Y"]],
        z=[nodes[i]["Z"], nodes[j]["Z"]],
        mode='lines',
        line=dict(color=color, width=width),
        showlegend=False,
        hoverinfo='none'
    ))

# Layout
fig.update_layout(
    title="3D Structural Frame",
    width=900, height=700,
    scene=dict(
        xaxis_title="X (m)",
        yaxis_title="Y (m)",
        zaxis_title="Level (m)",
        aspectmode="manual",
        aspectratio=dict(x=1, y=1, z=0.8)
    )
)
fig.show()

Example 4: Energy performance heatmap

import plotly.express as px
import numpy as np
import pandas as pd

# Simulate temperature data for a 24-hour period across 8 zones
hours = list(range(24))
zones = ["Zone A", "Zone B", "Zone C", "Zone D", "Zone E", "Zone F", "Zone G", "Zone H"]

# Generate realistic temperature variation
np.random.seed(42)
temp_data = np.zeros((len(zones), len(hours)))
for i, zone in enumerate(zones):
    base_temp = 20 + np.random.uniform(-2, 2)
    for j, hour in enumerate(hours):
        diurnal = 5 * np.sin(np.pi * (hour - 6) / 12)  # peak at 12:00
        noise = np.random.normal(0, 0.5)
        temp_data[i, j] = base_temp + diurnal + noise

df_heat = pd.DataFrame(temp_data, index=zones, columns=hours)
df_heat.index.name = "Zone"
df_heat.columns.name = "Hour"

fig = px.imshow(df_heat, 
                x=hours, y=zones,
                title="Zone Temperature Variation (°C) – 24 Hour Period",
                color_continuous_scale="RdYlBu_r",
                labels=dict(x="Hour of Day", y="Zone", color="°C"),
                aspect="auto")

fig.update_xaxes(side="bottom")
fig.show()

Example 5: Dashboards with subplots

import plotly.graph_objects as go
from plotly.subplots import make_subplots
import numpy as np

# Create a 2x2 dashboard
fig = make_subplots(
    rows=2, cols=2,
    subplot_titles=("Beam Moments", "Column Loads", "Deflection vs Span", "Material Quantities"),
    specs=[[{"type": "bar"}, {"type": "bar"}],
           [{"type": "scatter"}, {"type": "pie"}]]
)

# Subplot 1: Beam moments
beams = ["B1", "B2", "B3", "B4", "B5"]
moments = [112.5, 45.6, 194.4, 68.8, 286.7]
fig.add_trace(go.Bar(x=beams, y=moments, name="Moment (kNm)", marker_color="steelblue"), row=1, col=1)

# Subplot 2: Column loads
cols = ["C1", "C2", "C3", "C4"]
loads = [850, 920, 780, 1100]
fig.add_trace(go.Bar(x=cols, y=loads, name="Load (kN)", marker_color="coral"), row=1, col=2)

# Subplot 3: Deflection vs span
spans = np.linspace(3, 10, 20)
deflections = 5 * 25 * spans**4 / (384 * 200000 * 120e6) * 1000  # mm
fig.add_trace(go.Scatter(x=spans, y=deflections, mode="lines+markers", 
                          name="Deflection (mm)"), row=2, col=1)

# Subplot 4: Material quantities pie chart
materials = ["Concrete", "Steel", "Timber", "Glass"]
quantities = [45.2, 20.8, 12.5, 8.3]
fig.add_trace(go.Pie(labels=materials, values=quantities, name="Volume (m³)"), row=2, col=2)

fig.update_layout(height=700, width=1000, title_text="Structural Design Dashboard")
fig.show()

4. Hands‑on Exercises (3–5 Problems)

Problem 1 – Interactive scatter of beam spans vs loads
Given data: beams = [("B1", 6.0, 25), ("B2", 4.5, 18), ("B3", 7.2, 30), ("B4", 5.0, 22), ("B5", 8.0, 35)]
Create a plotly scatter plot with span on x-axis, load on y-axis, and beam mark as hover text. Colour points by load magnitude.

Problem 2 – 3D column grid visualisation
Create a 5×4 column grid with x-spacing 7.5m and y-spacing 9.0m. Plot all columns at z=0. Use px.scatter_3d and add labels for each column.

Problem 3 – Heatmap of material cost per zone
Given a 4×6 grid of zones with random unit costs between 50 and 150 $/m³, create a heatmap using px.imshow. Add proper axis labels and a title.

Problem 4 – Dashboard with two subplots
Create a 1×2 dashboard showing:

  • Left: Bar chart of floor areas for Levels 1–6
  • Right: Pie chart of space usage (Office, Retail, MEP, Roof)

Solutions (attempt first):

# P1
import plotly.express as px
import pandas as pd
data = [("B1", 6.0, 25), ("B2", 4.5, 18), ("B3", 7.2, 30), ("B4", 5.0, 22), ("B5", 8.0, 35)]
df = pd.DataFrame(data, columns=["Beam", "Span", "Load"])
fig = px.scatter(df, x="Span", y="Load", text="Beam", color="Load",
                 title="Beam Spans vs Loads", color_continuous_scale="Viridis")
fig.update_traces(textposition="top center", marker_size=12)
fig.show()

# P2
import plotly.express as px
import numpy as np
import pandas as pd
x = np.arange(5) * 7.5
y = np.arange(4) * 9.0
X, Y = np.meshgrid(x, y)
df = pd.DataFrame({"X": X.flatten(), "Y": Y.flatten(), "Z": np.zeros(20),
                   "Column": [f"C{i+1}" for i in range(20)]})
fig = px.scatter_3d(df, x="X", y="Y", z="Z", text="Column", title="5×4 Column Grid")
fig.update_traces(marker_size=6)
fig.show()

# P3
import numpy as np
import plotly.express as px
costs = np.random.uniform(50, 150, size=(4, 6))
fig = px.imshow(costs, x=[f"Grid {i+1}" for i in range(6)], y=[f"Row {i+1}" for i in range(4)],
                title="Material Cost per Zone ($/m³)", color_continuous_scale="RdYlGn")
fig.update_xaxes(title="Column Grid"); fig.update_yaxes(title="Row Grid")
fig.show()

# P4
from plotly.subplots import make_subplots
import plotly.graph_objects as go
floors = ["Level 1", "Level 2", "Level 3", "Level 4", "Level 5", "Level 6"]
areas = [300, 270, 270, 270, 150, 100]
usages = {"Office": 810, "Retail": 300, "MEP": 150, "Roof Terrace": 100}
fig = make_subplots(rows=1, cols=2, subplot_titles=("Floor Areas", "Space Usage"))
fig.add_trace(go.Bar(x=floors, y=areas, marker_color="steelblue"), row=1, col=1)
fig.add_trace(go.Pie(labels=list(usages.keys()), values=list(usages.values())), row=1, col=2)
fig.update_layout(height=500, width=1000, title_text="Building Summary Dashboard")
fig.show()

5. Applied Challenge Task

Task: Interactive Structural Design Dashboard

Build an interactive dashboard that displays the following for a 2‑storey steel frame building:

  1. 3D frame view – Show columns and beams in 3D space (use go.Scatter3d).
  2. Beam utilisation chart – Interactive bar chart showing utilisation % for each beam, colour‑coded (green < 60%, yellow 60–85%, red > 85%).
  3. Column load distribution – Horizontal bar chart showing axial load in each column.
  4. Floor plan heatmap – Show live load distribution across a 4×6 grid of zones on the ground floor (random values between 2.0 and 5.0 kN/m²).

All charts should be interactive (hover labels, zoom/pan). Use make_subplots to arrange them in a 2×2 grid. Save the final dashboard as structural_dashboard.html.

Bonus:

  • Add dropdown menus to filter beams by utilisation range.
  • Add a slider to adjust the live load intensity and see the impact on column loads.

Why this matters:
Interactive dashboards are increasingly used in design reviews, client presentations, and interdisciplinary coordination. They allow non‑technical stakeholders to explore engineering data without needing to understand the underlying calculations.


6. Brief Review Summary

  • plotly.express provides high‑level functions for interactive charts (bar, scatter, heatmap, 3D).
  • plotly.graph_objects gives lower‑level control for complex visualisations (3D frames, subplots).
  • fig.show() displays in browser; fig.write_html() saves as standalone HTML.
  • 3D scatter and line plots visualise structural frames and column grids.
  • Heatmaps are ideal for spatial data (U‑values, temperatures, loads).
  • Subplots create dashboards combining multiple chart types.

Key takeaway:
You can now create professional, interactive visualisations of structural and building performance data. These HTML files can be shared with anyone – no Python installation needed – making your analysis accessible to the entire project team.


7. Preview of Next Topic (Day 22)

Tomorrow we’ll cover Scripting CAD Environments – Rhino/Grasshopper Python and Dynamo Python.
You’ll learn:

  • How to write Python scripts inside Rhino/Grasshopper (ghPython) and Dynamo for Revit.
  • Creating geometry programmatically (points, curves, surfaces, Breps).
  • Automating repetitive modelling tasks (generating columns, beams, panels).
  • Reading/writing geometry from/to external files (CSV, JSON, IFC).
  • Practical example: generating a parametric truss directly in Rhino from a Python script.

This connects your Python skills directly to the modelling tools used daily in AEC practice.

Comments

EARN AT THE COMFORT OF YOUR HOME

Sponsored content

Popular posts from this blog

Day 5 – Functions, Scope, and Docstrings

Day 10 – Exception Handling and Debugging

Day 1 – Python Foundations for AEC Professionals