Spaces:
Sleeping
Sleeping
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +122 -103
src/streamlit_app.py
CHANGED
|
@@ -63,31 +63,22 @@ st.markdown("""
|
|
| 63 |
def main():
|
| 64 |
st.markdown('<div class="main-header">π Physics Tutorial: Kinematics</div>', unsafe_allow_html=True)
|
| 65 |
|
| 66 |
-
#
|
| 67 |
st.sidebar.title("π Select Tutorial")
|
| 68 |
-
tutorial_type = st.sidebar.
|
| 69 |
"Choose a physics concept:",
|
| 70 |
["1D Motion", "2D Projectile Motion", "Compare Motions"]
|
| 71 |
)
|
| 72 |
|
| 73 |
-
#
|
| 74 |
-
if 'current_tutorial' not in st.session_state:
|
| 75 |
-
st.session_state.current_tutorial = tutorial_type
|
| 76 |
-
elif st.session_state.current_tutorial != tutorial_type:
|
| 77 |
-
# Clear ALL cached data when switching tabs
|
| 78 |
-
st.cache_data.clear()
|
| 79 |
-
st.session_state.current_tutorial = tutorial_type
|
| 80 |
-
# Also clear any session state variables that might persist
|
| 81 |
-
keys_to_clear = [k for k in st.session_state.keys() if k.startswith(('comp_', '1d_', '2d_'))]
|
| 82 |
-
for key in keys_to_clear:
|
| 83 |
-
del st.session_state[key]
|
| 84 |
-
st.rerun()
|
| 85 |
-
|
| 86 |
if tutorial_type == "1D Motion":
|
|
|
|
| 87 |
show_1d_motion()
|
| 88 |
elif tutorial_type == "2D Projectile Motion":
|
|
|
|
| 89 |
show_2d_motion()
|
| 90 |
elif tutorial_type == "Compare Motions":
|
|
|
|
| 91 |
show_motion_comparison()
|
| 92 |
|
| 93 |
def create_1d_motion_plot(motion: Motion1D, duration: float, title: str):
|
|
@@ -686,106 +677,134 @@ def show_motion_comparison():
|
|
| 686 |
|
| 687 |
st.markdown("**Compare up to 3 different projectile motions side by side**")
|
| 688 |
|
| 689 |
-
# Use
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
|
|
|
|
|
|
| 709 |
|
|
|
|
| 710 |
st.markdown("---")
|
| 711 |
|
| 712 |
-
#
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
for name, motion, color in trajectories_data:
|
| 723 |
-
data = motion.trajectory_data(motion.calculate_flight_time())
|
| 724 |
-
|
| 725 |
-
# Add trajectory line
|
| 726 |
-
fig.add_trace(go.Scatter(
|
| 727 |
-
x=data['x'],
|
| 728 |
-
y=data['y'],
|
| 729 |
-
mode='lines',
|
| 730 |
-
name=name,
|
| 731 |
-
line=dict(color=color, width=3),
|
| 732 |
-
hovertemplate=f'<b>{name}</b><br>X: %{{x:.1f}} m<br>Y: %{{y:.1f}} m<extra></extra>'
|
| 733 |
-
))
|
| 734 |
|
| 735 |
-
|
| 736 |
-
fig.add_trace(go.Scatter(
|
| 737 |
-
x=[motion.launch_x],
|
| 738 |
-
y=[motion.launch_height],
|
| 739 |
-
mode='markers',
|
| 740 |
-
name=f'{name} Launch',
|
| 741 |
-
marker=dict(color=color, size=10, symbol='circle'),
|
| 742 |
-
showlegend=False,
|
| 743 |
-
hovertemplate=f'<b>{name} Launch</b><br>X: %{{x:.1f}} m<br>Y: %{{y:.1f}} m<extra></extra>'
|
| 744 |
-
))
|
| 745 |
|
| 746 |
-
|
| 747 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 748 |
fig.add_trace(go.Scatter(
|
| 749 |
-
x=[
|
| 750 |
-
y=[
|
| 751 |
mode='markers',
|
| 752 |
-
name=f'{name}
|
| 753 |
-
marker=dict(color=color, size=10, symbol='
|
| 754 |
showlegend=False,
|
| 755 |
-
hovertemplate=f'<b>{name}
|
| 756 |
))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 757 |
|
| 758 |
-
#
|
| 759 |
-
fig
|
| 760 |
-
|
| 761 |
-
xaxis_title="Horizontal Position (m)",
|
| 762 |
-
yaxis_title="Vertical Position (m)",
|
| 763 |
-
showlegend=True,
|
| 764 |
-
template='plotly_white',
|
| 765 |
-
height=600
|
| 766 |
-
)
|
| 767 |
-
|
| 768 |
-
fig.update_xaxes(range=[0, None], showgrid=True, gridcolor='rgba(128,128,128,0.3)')
|
| 769 |
-
fig.update_yaxes(range=[0, None], showgrid=True, gridcolor='rgba(128,128,128,0.3)')
|
| 770 |
-
|
| 771 |
-
# Display with unique key
|
| 772 |
-
st.plotly_chart(fig, use_container_width=True, key="simple_comparison_plot")
|
| 773 |
|
| 774 |
-
#
|
| 775 |
st.markdown("### π Trajectory Comparison Table")
|
| 776 |
-
comparison_data =
|
| 777 |
-
for name, motion in [(name, motion) for name, motion, _ in trajectories_data]:
|
| 778 |
-
info = motion.get_launch_info()
|
| 779 |
-
comparison_data.append({
|
| 780 |
-
"Trajectory": name,
|
| 781 |
-
"Speed (m/s)": f"{info['launch_speed']:.1f}",
|
| 782 |
-
"Angle (Β°)": f"{info['launch_angle']:.1f}",
|
| 783 |
-
"Height (m)": f"{info['launch_height']:.1f}",
|
| 784 |
-
"Flight Time (s)": f"{info['flight_time']:.2f}",
|
| 785 |
-
"Range (m)": f"{info['range']:.1f}",
|
| 786 |
-
"Max Height (m)": f"{info['max_height']:.1f}"
|
| 787 |
-
})
|
| 788 |
-
|
| 789 |
st.table(comparison_data)
|
| 790 |
|
| 791 |
if __name__ == "__main__":
|
|
|
|
| 63 |
def main():
|
| 64 |
st.markdown('<div class="main-header">π Physics Tutorial: Kinematics</div>', unsafe_allow_html=True)
|
| 65 |
|
| 66 |
+
# Use selectbox instead of radio (forces full page refresh)
|
| 67 |
st.sidebar.title("π Select Tutorial")
|
| 68 |
+
tutorial_type = st.sidebar.selectbox(
|
| 69 |
"Choose a physics concept:",
|
| 70 |
["1D Motion", "2D Projectile Motion", "Compare Motions"]
|
| 71 |
)
|
| 72 |
|
| 73 |
+
# Force complete refresh when changing tutorials
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
if tutorial_type == "1D Motion":
|
| 75 |
+
st.cache_data.clear() # Clear all caches
|
| 76 |
show_1d_motion()
|
| 77 |
elif tutorial_type == "2D Projectile Motion":
|
| 78 |
+
st.cache_data.clear() # Clear all caches
|
| 79 |
show_2d_motion()
|
| 80 |
elif tutorial_type == "Compare Motions":
|
| 81 |
+
st.cache_data.clear() # Clear all caches
|
| 82 |
show_motion_comparison()
|
| 83 |
|
| 84 |
def create_1d_motion_plot(motion: Motion1D, duration: float, title: str):
|
|
|
|
| 677 |
|
| 678 |
st.markdown("**Compare up to 3 different projectile motions side by side**")
|
| 679 |
|
| 680 |
+
# Use container with unique identification for comparison section
|
| 681 |
+
with st.container(key="comparison_container"):
|
| 682 |
+
# Use columns instead of tabs to avoid tab-related flickering
|
| 683 |
+
col1, col2, col3 = st.columns(3)
|
| 684 |
+
|
| 685 |
+
with col1:
|
| 686 |
+
st.subheader("π Trajectory 1")
|
| 687 |
+
speed1 = st.slider("Speed 1", 5.0, 50.0, 20.0, key="comp_speed1")
|
| 688 |
+
angle1 = st.slider("Angle 1", 0.0, 90.0, 30.0, key="comp_angle1")
|
| 689 |
+
height1 = st.slider("Height 1", 0.0, 30.0, 0.0, key="comp_height1")
|
| 690 |
+
|
| 691 |
+
with col2:
|
| 692 |
+
st.subheader("π― Trajectory 2")
|
| 693 |
+
speed2 = st.slider("Speed 2", 5.0, 50.0, 25.0, key="comp_speed2")
|
| 694 |
+
angle2 = st.slider("Angle 2", 0.0, 90.0, 45.0, key="comp_angle2")
|
| 695 |
+
height2 = st.slider("Height 2", 0.0, 30.0, 0.0, key="comp_height2")
|
| 696 |
+
|
| 697 |
+
with col3:
|
| 698 |
+
st.subheader("β½ Trajectory 3")
|
| 699 |
+
speed3 = st.slider("Speed 3", 5.0, 50.0, 30.0, key="comp_speed3")
|
| 700 |
+
angle3 = st.slider("Angle 3", 0.0, 90.0, 60.0, key="comp_angle3")
|
| 701 |
+
height3 = st.slider("Height 3", 0.0, 30.0, 5.0, key="comp_height3")
|
| 702 |
|
| 703 |
+
# Single plot and table section
|
| 704 |
st.markdown("---")
|
| 705 |
|
| 706 |
+
# Create UNIQUE comparison plot function that can't conflict with 2D motion
|
| 707 |
+
@st.cache_data(show_spinner=False)
|
| 708 |
+
def create_trajectory_comparison_plot_unique(tab_id, speed1, angle1, height1, speed2, angle2, height2, speed3, angle3, height3):
|
| 709 |
+
"""UNIQUE comparison plot function - completely separate from 2D motion"""
|
| 710 |
+
trajectories_data = [
|
| 711 |
+
("Trajectory 1", Motion2D(launch_speed=speed1, launch_angle=angle1, launch_height=height1), "blue"),
|
| 712 |
+
("Trajectory 2", Motion2D(launch_speed=speed2, launch_angle=angle2, launch_height=height2), "red"),
|
| 713 |
+
("Trajectory 3", Motion2D(launch_speed=speed3, launch_angle=angle3, launch_height=height3), "green")
|
| 714 |
+
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 715 |
|
| 716 |
+
fig = go.Figure()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 717 |
|
| 718 |
+
for name, motion, color in trajectories_data:
|
| 719 |
+
data = motion.trajectory_data(motion.calculate_flight_time())
|
| 720 |
+
|
| 721 |
+
# Add trajectory line
|
| 722 |
+
fig.add_trace(go.Scatter(
|
| 723 |
+
x=data['x'],
|
| 724 |
+
y=data['y'],
|
| 725 |
+
mode='lines',
|
| 726 |
+
name=name,
|
| 727 |
+
line=dict(color=color, width=3),
|
| 728 |
+
hovertemplate=f'<b>{name}</b><br>X: %{{x:.1f}} m<br>Y: %{{y:.1f}} m<extra></extra>'
|
| 729 |
+
))
|
| 730 |
+
|
| 731 |
+
# Add launch point
|
| 732 |
fig.add_trace(go.Scatter(
|
| 733 |
+
x=[motion.launch_x],
|
| 734 |
+
y=[motion.launch_height],
|
| 735 |
mode='markers',
|
| 736 |
+
name=f'{name} Launch',
|
| 737 |
+
marker=dict(color=color, size=10, symbol='circle'),
|
| 738 |
showlegend=False,
|
| 739 |
+
hovertemplate=f'<b>{name} Launch</b><br>X: %{{x:.1f}} m<br>Y: %{{y:.1f}} m<extra></extra>'
|
| 740 |
))
|
| 741 |
+
|
| 742 |
+
# Add landing point
|
| 743 |
+
if len(data['x']) > 0:
|
| 744 |
+
fig.add_trace(go.Scatter(
|
| 745 |
+
x=[data['x'][-1]],
|
| 746 |
+
y=[data['y'][-1]],
|
| 747 |
+
mode='markers',
|
| 748 |
+
name=f'{name} Landing',
|
| 749 |
+
marker=dict(color=color, size=10, symbol='square'),
|
| 750 |
+
showlegend=False,
|
| 751 |
+
hovertemplate=f'<b>{name} Landing</b><br>X: %{{x:.1f}} m<br>Y: %{{y:.1f}} m<extra></extra>'
|
| 752 |
+
))
|
| 753 |
+
|
| 754 |
+
# Update layout
|
| 755 |
+
fig.update_layout(
|
| 756 |
+
title=dict(text="Multi-Trajectory Comparison", font=dict(size=18, color="black")),
|
| 757 |
+
xaxis_title="Horizontal Position (m)",
|
| 758 |
+
yaxis_title="Vertical Position (m)",
|
| 759 |
+
showlegend=True,
|
| 760 |
+
hovermode='closest',
|
| 761 |
+
template='plotly_white',
|
| 762 |
+
height=600,
|
| 763 |
+
margin=dict(l=50, r=50, t=80, b=50)
|
| 764 |
+
)
|
| 765 |
+
|
| 766 |
+
# Set axis ranges
|
| 767 |
+
fig.update_xaxes(range=[0, None])
|
| 768 |
+
fig.update_yaxes(range=[0, None])
|
| 769 |
+
|
| 770 |
+
# Add grid
|
| 771 |
+
fig.update_xaxes(showgrid=True, gridwidth=1, gridcolor='rgba(128,128,128,0.3)')
|
| 772 |
+
fig.update_yaxes(showgrid=True, gridwidth=1, gridcolor='rgba(128,128,128,0.3)')
|
| 773 |
+
|
| 774 |
+
return fig
|
| 775 |
+
|
| 776 |
+
# Create cached comparison data with unique function name
|
| 777 |
+
@st.cache_data(show_spinner=False)
|
| 778 |
+
def create_trajectory_comparison_data_unique(tab_id, speed1, angle1, height1, speed2, angle2, height2, speed3, angle3, height3):
|
| 779 |
+
"""UNIQUE comparison data function"""
|
| 780 |
+
trajectories_data = [
|
| 781 |
+
("Trajectory 1", Motion2D(launch_speed=speed1, launch_angle=angle1, launch_height=height1)),
|
| 782 |
+
("Trajectory 2", Motion2D(launch_speed=speed2, launch_angle=angle2, launch_height=height2)),
|
| 783 |
+
("Trajectory 3", Motion2D(launch_speed=speed3, launch_angle=angle3, launch_height=height3))
|
| 784 |
+
]
|
| 785 |
+
|
| 786 |
+
comparison_data = []
|
| 787 |
+
for name, motion in trajectories_data:
|
| 788 |
+
info = motion.get_launch_info()
|
| 789 |
+
comparison_data.append({
|
| 790 |
+
"Trajectory": name,
|
| 791 |
+
"Speed (m/s)": f"{info['launch_speed']:.1f}",
|
| 792 |
+
"Angle (Β°)": f"{info['launch_angle']:.1f}",
|
| 793 |
+
"Height (m)": f"{info['launch_height']:.1f}",
|
| 794 |
+
"Flight Time (s)": f"{info['flight_time']:.2f}",
|
| 795 |
+
"Range (m)": f"{info['range']:.1f}",
|
| 796 |
+
"Max Height (m)": f"{info['max_height']:.1f}"
|
| 797 |
+
})
|
| 798 |
+
|
| 799 |
+
return comparison_data
|
| 800 |
|
| 801 |
+
# Display Plotly chart with completely unique identifiers
|
| 802 |
+
fig = create_trajectory_comparison_plot_unique("comparison_tab", speed1, angle1, height1, speed2, angle2, height2, speed3, angle3, height3)
|
| 803 |
+
st.plotly_chart(fig, use_container_width=True, key="trajectory_comparison_plot_unique")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 804 |
|
| 805 |
+
# Display comparison table
|
| 806 |
st.markdown("### π Trajectory Comparison Table")
|
| 807 |
+
comparison_data = create_trajectory_comparison_data_unique("comparison_tab", speed1, angle1, height1, speed2, angle2, height2, speed3, angle3, height3)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 808 |
st.table(comparison_data)
|
| 809 |
|
| 810 |
if __name__ == "__main__":
|