Create an app showing 3D data visualizations. Generate mock 3D data: - Spiral helix with noise (100 points) - Surface plot (z = sin(x)*cos(y)) - 3D clusters (3 clusters, 50 points each) Features: - Viz selector dropdown - Interactive 3D plots with rotate/zoom - Controls for: * Point styling (size, color, opacity) * Axis settings * Animation speed Surface plot options: - Function parameter sliders - Color scheme toggle - Contour lines All plots should be fully interactive.
To upload files, please first save the app
import streamlit as st
import numpy as np
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
from sklearn.datasets import make_blobs
import time
st.set_page_config(page_title="3D Data Visualizations", layout="wide")
# Main app title
st.title("Interactive 3D Data Visualizations")
# Sidebar for plot selection and global controls
st.sidebar.header("Visualization Controls")
# Visualization selector
viz_type = st.sidebar.selectbox(
"Select Visualization",
["3D Spiral Helix", "3D Surface Plot", "3D Clusters"]
)
# Global point styling controls
st.sidebar.subheader("Point Styling")
point_size = st.sidebar.slider("Point Size", 2, 15, 5)
point_opacity = st.sidebar.slider("Point Opacity", 0.1, 1.0, 0.8)
# Animation controls
st.sidebar.subheader("Animation")
animation_speed = st.sidebar.slider("Animation Speed", 0.01, 1.0, 0.1)
# Axis settings
st.sidebar.subheader("Axis Settings")
show_axis = st.sidebar.checkbox("Show Axis", True)
show_grid = st.sidebar.checkbox("Show Grid", True)
# Color settings
color_scheme = st.sidebar.selectbox(
"Color Scheme",
["Viridis", "Plasma", "Inferno", "Magma", "Cividis", "Rainbow"]
)
# Function to generate spiral helix data
@st.cache_data
def generate_spiral_data(points=100, noise=0.5):
t = np.linspace(0, 10*np.pi, points)
x = np.cos(t)
y = np.sin(t)
z = t/10
# Add some noise
x += np.random.normal(0, noise, points) * 0.1
y += np.random.normal(0, noise, points) * 0.1
z += np.random.normal(0, noise, points) * 0.1
return pd.DataFrame({
'x': x,
'y': y,
'z': z,
't': t
})
# Function to generate 3D surface data
@st.cache_data
def generate_surface_data(x_range=(-5, 5), y_range=(-5, 5), points=50):
x = np.linspace(x_range[0], x_range[1], points)
y = np.linspace(y_range[0], y_range[1], points)
x_grid, y_grid = np.meshgrid(x, y)
return x_grid, y_grid
# Function to calculate z values for surface
def calculate_surface_z(x_grid, y_grid, a=1, b=1, c=0, d=0):
return a * np.sin(b * x_grid + c) * np.cos(d + b * y_grid)
# Function to generate 3D cluster data
@st.cache_data
def generate_cluster_data(n_clusters=3, points_per_cluster=50, random_state=42):
X, y = make_blobs(
n_samples=n_clusters * points_per_cluster,
centers=n_clusters,
cluster_std=0.7,
random_state=random_state,
n_features=3
)
return pd.DataFrame({
'x': X[:, 0],
'y': X[:, 1],
'z': X[:, 2],
'cluster': y
})
# Mapping of color scheme names to Plotly color scales
color_scale_map = {
"Viridis": "Viridis",
"Plasma": "Plasma",
"Inferno": "Inferno",
"Magma": "Magma",
"Cividis": "Cividis",
"Rainbow": "Rainbow"
}
# Initialize session state for animation
if 'animation_frame' not in st.session_state:
st.session_state.animation_frame = 0
st.session_state.animation_running = False
st.session_state.last_update_time = time.time()
# Main visualization logic
if viz_type == "3D Spiral Helix":
st.header("3D Spiral Helix with Noise")
# Additional controls for helix
noise_level = st.slider("Noise Level", 0.0, 2.0, 0.5)
# Generate data
spiral_data = generate_spiral_data(points=100, noise=noise_level)
# Animation controls
col1, col2 = st.columns([1, 4])
with col1:
if st.button("Start/Stop Animation"):
st.session_state.animation_running = not st.session_state.animation_running
# Create figure
fig = go.Figure()
# Update animation frame if animation is running
if st.session_state.animation_running:
current_time = time.time()
if current_time - st.session_state.last_update_time > 1 - animation_speed:
st.session_state.animation_frame = (st.session_state.animation_frame + 1) % 100
st.session_state.last_update_time = current_time
# Add trace
marker_color = spiral_data['t']
if st.session_state.animation_running:
# Highlight points in a moving window
highlight = np.abs((np.arange(100) - st.session_state.animation_frame) % 100)
highlight = 1 - np.clip(highlight / 10, 0, 1)
marker_size = point_size * (1 + 2 * highlight)
else:
marker_size = point_size
fig.add_trace(go.Scatter3d(
x=spiral_data['x'],
y=spiral_data['y'],
z=spiral_data['z'],
mode='markers',
marker=dict(
size=marker_size,
color=marker_color,
colorscale=color_scale_map[color_scheme],
opacity=point_opacity
),
hoverinfo='text',
text=[f'Point {i}<br>x: {x:.2f}, y: {y:.2f}, z: {z:.2f}'
for i, (x, y, z) in enumerate(zip(spiral_data['x'], spiral_data['y'], spiral_data['z']))]
))
# Add lines to connect points for better visualization
fig.add_trace(go.Scatter3d(
x=spiral_data['x'],
y=spiral_data['y'],
z=spiral_data['z'],
mode='lines',
line=dict(
color='rgba(100, 100, 100, 0.3)',
width=2
),
hoverinfo='none'
))
# Layout
fig.update_layout(
scene=dict(
xaxis_visible=show_axis,
yaxis_visible=show_axis,
zaxis_visible=show_axis,
xaxis_showgrid=show_grid,
yaxis_showgrid=show_grid,
zaxis_showgrid=show_grid,
),
margin=dict(l=0, r=0, b=0, t=0),
height=700
)
with col2:
st.plotly_chart(fig, use_container_width=True)
st.caption("Rotate, zoom, and pan the visualization using your mouse. The spiral helix shows a 3D path with added noise.")
elif viz_type == "3D Surface Plot":
st.header("3D Surface Plot")
# Additional controls for surface plot
col1, col2 = st.columns(2)
with col1:
a_param = st.slider("Amplitude (a)", 0.1, 2.0, 1.0)
b_param = st.slider("Frequency (b)", 0.1, 2.0, 1.0)
with col2:
c_param = st.slider("Phase Shift X (c)", 0.0, np.pi, 0.0)
d_param = st.slider("Phase Shift Y (d)", 0.0, np.pi, 0.0)
show_contours = st.checkbox("Show Contour Lines", True)
# Generate data
x_grid, y_grid = generate_surface_data()
z_grid = calculate_surface_z(x_grid, y_grid, a=a_param, b=b_param, c=c_param, d=d_param)
# Create figure
fig = go.Figure()
# Add surface
fig.add_trace(go.Surface(
x=x_grid,
y=y_grid,
z=z_grid,
colorscale=color_scale_map[color_scheme],
opacity=point_opacity,
contours={
"z": {"show": show_contours, "start": -2, "end": 2, "size": 0.1}
}
))
# Add formula display
formula = f"z = {a_param:.2f} × sin({b_param:.2f}x + {c_param:.2f}) × cos({d_param:.2f} + {b_param:.2f}y)"
# Layout
fig.update_layout(
scene=dict(
xaxis_visible=show_axis,
yaxis_visible=show_axis,
zaxis_visible=show_axis,
xaxis_showgrid=show_grid,
yaxis_showgrid=show_grid,
zaxis_showgrid=show_grid,
xaxis_title="X",
yaxis_title="Y",
zaxis_title="Z",
camera=dict(
eye=dict(x=1.5, y=1.5, z=1.2)
)
),
margin=dict(l=0, r=0, b=0, t=0),
height=700
)
st.plotly_chart(fig, use_container_width=True)
st.markdown(f"**Current formula:** {formula}")
st.caption("Adjust the parameters to see how the surface changes. The surface represents z = a × sin(bx + c) × cos(d + by).")
elif viz_type == "3D Clusters":
st.header("3D Clusters")
# Additional controls for clusters
col1, col2 = st.columns(2)
with col1:
n_clusters = st.slider("Number of Clusters", 2, 5, 3)
with col2:
points_per_cluster = st.slider("Points per Cluster", 20, 100, 50)
random_seed = st.slider("Random Seed", 0, 100, 42)
# Generate data
cluster_data = generate_cluster_data(
n_clusters=n_clusters,
points_per_cluster=points_per_cluster,
random_state=random_seed
)
# Create figure
if color_scheme == "Rainbow":
fig = px.scatter_3d(
cluster_data, x='x', y='y', z='z',
color='cluster',
size_max=point_size,
opacity=point_opacity,
color_discrete_sequence=px.colors.qualitative.Plotly
)
else:
fig = px.scatter_3d(
cluster_data, x='x', y='y', z='z',
color='cluster',
size_max=point_size,
opacity=point_opacity,
color_discrete_sequence=px.colors.qualitative.Plotly
)
# Customize markers
fig.update_traces(marker=dict(size=point_size))
# Draw hulls around clusters if wanted
draw_hulls = st.checkbox("Draw Cluster Hulls", False)
if draw_hulls:
for cluster_id in range(n_clusters):
cluster_points = cluster_data[cluster_data['cluster'] == cluster_id]
x, y, z = cluster_points['x'], cluster_points['y'], cluster_points['z']
# Calculate simple centroid
centroid_x, centroid_y, centroid_z = x.mean(), y.mean(), z.mean()
# Add lines from centroid to each point in the cluster
for i in range(len(x)):
fig.add_trace(go.Scatter3d(
x=[centroid_x, x.iloc[i]],
y=[centroid_y, y.iloc[i]],
z=[centroid_z, z.iloc[i]],
mode='lines',
line=dict(color=f'rgba(100,100,100,0.2)'),
showlegend=False
))
# Layout
fig.update_layout(
scene=dict(
xaxis_visible=show_axis,
yaxis_visible=show_axis,
zaxis_visible=show_axis,
xaxis_showgrid=show_grid,
yaxis_showgrid=show_grid,
zaxis_showgrid=show_grid,
),
margin=dict(l=0, r=0, b=0, t=0),
height=700
)
st.plotly_chart(fig, use_container_width=True)
# Cluster statistics
st.subheader("Cluster Statistics")
cluster_stats = cluster_data.groupby('cluster').agg({
'x': ['mean', 'std'],
'y': ['mean', 'std'],
'z': ['mean', 'std']
}).reset_index()
cluster_stats.columns = ['Cluster', 'X Mean', 'X StdDev', 'Y Mean', 'Y StdDev', 'Z Mean', 'Z StdDev']
st.dataframe(cluster_stats.round(3))
st.caption("This visualization shows randomly generated clusters in 3D space. Each color represents a different cluster.")
# Footer
st.markdown("---")
st.caption("Interactive 3D Data Visualization | Created with Streamlit and Plotly")
Hi! I can help you with any questions about Streamlit and Python. What would you like to know?