NavyDevilDoc commited on
Commit
26fddb4
Β·
verified Β·
1 Parent(s): 114aa49

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. 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
- # Sidebar for navigation
67
  st.sidebar.title("πŸ“š Select Tutorial")
68
- tutorial_type = st.sidebar.radio(
69
  "Choose a physics concept:",
70
  ["1D Motion", "2D Projectile Motion", "Compare Motions"]
71
  )
72
 
73
- # Clear session state and cache when switching tabs
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 columns for parameters
690
- col1, col2, col3 = st.columns(3)
691
-
692
- with col1:
693
- st.subheader("πŸš€ Trajectory 1")
694
- speed1 = st.slider("Speed 1", 5.0, 50.0, 20.0, key="comp_speed1")
695
- angle1 = st.slider("Angle 1", 0.0, 90.0, 30.0, key="comp_angle1")
696
- height1 = st.slider("Height 1", 0.0, 30.0, 0.0, key="comp_height1")
697
-
698
- with col2:
699
- st.subheader("🎯 Trajectory 2")
700
- speed2 = st.slider("Speed 2", 5.0, 50.0, 25.0, key="comp_speed2")
701
- angle2 = st.slider("Angle 2", 0.0, 90.0, 45.0, key="comp_angle2")
702
- height2 = st.slider("Height 2", 0.0, 30.0, 0.0, key="comp_height2")
703
-
704
- with col3:
705
- st.subheader("⚽ Trajectory 3")
706
- speed3 = st.slider("Speed 3", 5.0, 50.0, 30.0, key="comp_speed3")
707
- angle3 = st.slider("Angle 3", 0.0, 90.0, 60.0, key="comp_height3")
708
- height3 = st.slider("Height 3", 0.0, 30.0, 5.0, key="comp_height3")
 
 
709
 
 
710
  st.markdown("---")
711
 
712
- # NO CACHING - Direct plot creation every time
713
- trajectories_data = [
714
- ("Trajectory 1", Motion2D(launch_speed=speed1, launch_angle=angle1, launch_height=height1), "blue"),
715
- ("Trajectory 2", Motion2D(launch_speed=speed2, launch_angle=angle2, launch_height=height2), "red"),
716
- ("Trajectory 3", Motion2D(launch_speed=speed3, launch_angle=angle3, launch_height=height3), "green")
717
- ]
718
-
719
- # Create fresh plot every time
720
- fig = go.Figure()
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
- # Add launch point
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
- # Add landing point
747
- if len(data['x']) > 0:
 
 
 
 
 
 
 
 
 
 
 
 
748
  fig.add_trace(go.Scatter(
749
- x=[data['x'][-1]],
750
- y=[data['y'][-1]],
751
  mode='markers',
752
- name=f'{name} Landing',
753
- marker=dict(color=color, size=10, symbol='square'),
754
  showlegend=False,
755
- hovertemplate=f'<b>{name} Landing</b><br>X: %{{x:.1f}} m<br>Y: %{{y:.1f}} m<extra></extra>'
756
  ))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
757
 
758
- # Update layout
759
- fig.update_layout(
760
- title=dict(text="Trajectory Comparison", font=dict(size=18)),
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
- # Create comparison table (no caching)
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__":