NavyDevilDoc commited on
Commit
96fd52c
Β·
verified Β·
1 Parent(s): e87d551

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +35 -139
src/streamlit_app.py CHANGED
@@ -1,38 +1,8 @@
1
  import streamlit as st
2
  import numpy as np
3
  import matplotlib.pyplot as plt
4
- import matplotlib
5
- import os
6
- import tempfile
7
  from kinematics_visualizer import Motion1D, Motion2D, KinematicsVisualizer
8
 
9
- # Fix matplotlib configuration for HuggingFace Spaces
10
- try:
11
- # Set matplotlib to use a writable temporary directory
12
- temp_dir = tempfile.mkdtemp()
13
- os.environ['MPLCONFIGDIR'] = temp_dir
14
- matplotlib.use('Agg') # Use non-interactive backend
15
- except Exception as e:
16
- st.warning(f"Matplotlib configuration warning (app will still work): {e}")
17
-
18
- def clear_comparison_state():
19
- """Clear any lingering comparison state"""
20
- try:
21
- keys_to_clear = [key for key in st.session_state.keys() if key.startswith('comp_')]
22
- for key in keys_to_clear:
23
- if key in st.session_state:
24
- del st.session_state[key]
25
- except Exception:
26
- pass # Silently handle any state clearing issues
27
-
28
- def clear_plots():
29
- """Safely clear all matplotlib plots"""
30
- try:
31
- plt.clf()
32
- plt.close('all')
33
- plt.ioff() # Turn off interactive mode
34
- except Exception:
35
- pass
36
 
37
  # Configure Streamlit page
38
  st.set_page_config(
@@ -90,47 +60,21 @@ st.markdown("""
90
  """, unsafe_allow_html=True)
91
 
92
  def main():
93
- # Clear plots at the start
94
- clear_plots()
95
-
96
  st.markdown('<div class="main-header">πŸš€ Physics Tutorial: Kinematics</div>', unsafe_allow_html=True)
97
 
98
- # Initialize session state for tutorial selection if not exists
99
- if 'tutorial_type' not in st.session_state:
100
- st.session_state.tutorial_type = "1D Motion"
101
-
102
  # Sidebar for navigation
103
  st.sidebar.title("πŸ“š Select Tutorial")
104
  tutorial_type = st.sidebar.radio(
105
  "Choose a physics concept:",
106
- ["1D Motion", "2D Projectile Motion", "Compare Motions"],
107
- index=["1D Motion", "2D Projectile Motion", "Compare Motions"].index(st.session_state.tutorial_type)
108
  )
109
 
110
- # Detect tutorial change and clear state
111
- if tutorial_type != st.session_state.tutorial_type:
112
- st.session_state.tutorial_type = tutorial_type
113
- clear_comparison_state()
114
- clear_plots()
115
- # Force a clean rerun
116
- st.rerun()
117
-
118
- # Clear comparison state when not on comparison tab
119
- if tutorial_type != "Compare Motions":
120
- clear_comparison_state()
121
- clear_plots()
122
-
123
- # Route to appropriate function
124
- try:
125
- if tutorial_type == "1D Motion":
126
- show_1d_motion()
127
- elif tutorial_type == "2D Projectile Motion":
128
- show_2d_motion()
129
- elif tutorial_type == "Compare Motions":
130
- show_motion_comparison()
131
- except Exception as e:
132
- st.error(f"Error loading tutorial: {str(e)}")
133
- st.info("Please refresh the page and try again.")
134
 
135
  def show_1d_motion():
136
  st.markdown('<div class="section-header">πŸ“ One-Dimensional Motion</div>', unsafe_allow_html=True)
@@ -530,115 +474,76 @@ def show_motion_comparison():
530
 
531
  st.markdown("**Compare up to 3 different projectile motions side by side**")
532
 
533
- # Clear any previous matplotlib figures explicitly
534
- clear_plots()
 
535
 
536
- # Use session state to prevent oscillation
537
- if 'comparison_initialized' not in st.session_state:
538
- st.session_state.comparison_initialized = True
539
- st.session_state.comp_speed1 = 20.0
540
- st.session_state.comp_angle1 = 30.0
541
- st.session_state.comp_height1 = 0.0
542
- st.session_state.comp_speed2 = 25.0
543
- st.session_state.comp_angle2 = 45.0
544
- st.session_state.comp_height2 = 0.0
545
- st.session_state.comp_speed3 = 30.0
546
- st.session_state.comp_angle3 = 60.0
547
- st.session_state.comp_height3 = 5.0
548
 
549
  # Create tabs for different trajectories
550
  tab1, tab2, tab3 = st.tabs(["πŸš€ Trajectory 1", "🎯 Trajectory 2", "⚽ Trajectory 3"])
551
 
552
  trajectories = []
553
 
554
- # Tab 1
555
  with tab1:
556
  with st.container():
557
- st.subheader("Configure Trajectory 1")
558
  col1, col2 = st.columns(2)
559
  with col1:
560
- speed1 = st.slider("Speed 1 (m/s)", 5.0, 50.0, st.session_state.comp_speed1, key="slider_speed1")
561
- angle1 = st.slider("Angle 1 (Β°)", 0.0, 90.0, st.session_state.comp_angle1, key="slider_angle1")
562
  with col2:
563
- height1 = st.slider("Height 1 (m)", 0.0, 30.0, st.session_state.comp_height1, key="slider_height1")
564
 
565
- # Update session state
566
- st.session_state.comp_speed1 = speed1
567
- st.session_state.comp_angle1 = angle1
568
- st.session_state.comp_height1 = height1
569
-
570
  motion1 = Motion2D(launch_speed=speed1, launch_angle=angle1, launch_height=height1)
571
  trajectories.append(("Trajectory 1", motion1, 'blue'))
572
 
573
- # Tab 2
574
  with tab2:
575
  with st.container():
576
- st.subheader("Configure Trajectory 2")
577
  col1, col2 = st.columns(2)
578
  with col1:
579
- speed2 = st.slider("Speed 2 (m/s)", 5.0, 50.0, st.session_state.comp_speed2, key="slider_speed2")
580
- angle2 = st.slider("Angle 2 (Β°)", 0.0, 90.0, st.session_state.comp_angle2, key="slider_angle2")
581
  with col2:
582
- height2 = st.slider("Height 2 (m)", 0.0, 30.0, st.session_state.comp_height2, key="slider_height2")
583
 
584
- # Update session state
585
- st.session_state.comp_speed2 = speed2
586
- st.session_state.comp_angle2 = angle2
587
- st.session_state.comp_height2 = height2
588
-
589
  motion2 = Motion2D(launch_speed=speed2, launch_angle=angle2, launch_height=height2)
590
  trajectories.append(("Trajectory 2", motion2, 'red'))
591
 
592
- # Tab 3
593
  with tab3:
594
  with st.container():
595
- st.subheader("Configure Trajectory 3")
596
  col1, col2 = st.columns(2)
597
  with col1:
598
- speed3 = st.slider("Speed 3 (m/s)", 5.0, 50.0, st.session_state.comp_speed3, key="slider_speed3")
599
- angle3 = st.slider("Angle 3 (Β°)", 0.0, 90.0, st.session_state.comp_angle3, key="slider_angle3")
600
  with col2:
601
- height3 = st.slider("Height 3 (m)", 0.0, 30.0, st.session_state.comp_height3, key="slider_height3")
602
 
603
- # Update session state
604
- st.session_state.comp_speed3 = speed3
605
- st.session_state.comp_angle3 = angle3
606
- st.session_state.comp_height3 = height3
607
-
608
  motion3 = Motion2D(launch_speed=speed3, launch_angle=angle3, launch_height=height3)
609
  trajectories.append(("Trajectory 3", motion3, 'green'))
610
 
611
- # Create comparison plot in a separate container
612
- st.markdown("---")
613
- st.markdown("### πŸ“ˆ Trajectory Comparison Plot")
614
-
615
- plot_container = st.container()
616
- with plot_container:
617
  try:
618
- # Create figure with explicit cleanup
619
  fig, ax = plt.subplots(figsize=(12, 8))
620
 
621
  max_range = 0
622
- valid_trajectories = 0
623
-
624
  for name, motion, color in trajectories:
625
  try:
626
  data = motion.trajectory_data(motion.calculate_flight_time())
627
  if len(data['x']) > 0:
628
  ax.plot(data['x'], data['y'], linewidth=3, label=name, color=color)
629
-
630
- # Mark launch and landing points
631
  ax.plot(motion.launch_x, motion.launch_height, 'o', markersize=8, color=color, alpha=0.7)
632
- if len(data['x']) > 0:
633
- ax.plot(data['x'][-1], data['y'][-1], 's', markersize=8, color=color, alpha=0.7)
634
- max_range = max(max_range, data['x'][-1] if data['x'][-1] > 0 else 0)
635
- valid_trajectories += 1
636
  except Exception as e:
637
- st.warning(f"Could not plot {name}: {str(e)}")
638
  continue
639
 
640
- # Only proceed if we have valid data
641
- if valid_trajectories > 0 and max_range > 0:
642
  ax.set_xlabel('Horizontal Position (m)', fontsize=12)
643
  ax.set_ylabel('Vertical Position (m)', fontsize=12)
644
  ax.set_title('Trajectory Comparison', fontsize=16, fontweight='bold')
@@ -647,7 +552,6 @@ def show_motion_comparison():
647
  ax.set_ylim(bottom=0)
648
  ax.set_xlim(0, max_range * 1.1)
649
 
650
- # Display plot with explicit cleanup
651
  st.pyplot(fig, clear_figure=True)
652
  else:
653
  st.warning("No valid trajectory data to display")
@@ -655,19 +559,12 @@ def show_motion_comparison():
655
  except Exception as e:
656
  st.error(f"Error creating comparison plot: {str(e)}")
657
  finally:
658
- # Always clean up
659
- try:
660
- plt.close(fig)
661
- plt.close('all')
662
- except:
663
- pass
664
-
665
- # Comparison table
666
- st.markdown("---")
667
- st.markdown("### πŸ“‹ Trajectory Comparison Table")
668
 
669
- table_container = st.container()
670
- with table_container:
 
 
671
  try:
672
  comparison_data = []
673
  for name, motion, _ in trajectories:
@@ -683,7 +580,6 @@ def show_motion_comparison():
683
  "Max Height (m)": f"{info['max_height']:.1f}"
684
  })
685
  except Exception as e:
686
- st.warning(f"Could not get data for {name}: {str(e)}")
687
  continue
688
 
689
  if comparison_data:
 
1
  import streamlit as st
2
  import numpy as np
3
  import matplotlib.pyplot as plt
 
 
 
4
  from kinematics_visualizer import Motion1D, Motion2D, KinematicsVisualizer
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
  # Configure Streamlit page
8
  st.set_page_config(
 
60
  """, unsafe_allow_html=True)
61
 
62
  def main():
 
 
 
63
  st.markdown('<div class="main-header">πŸš€ Physics Tutorial: Kinematics</div>', unsafe_allow_html=True)
64
 
 
 
 
 
65
  # Sidebar for navigation
66
  st.sidebar.title("πŸ“š Select Tutorial")
67
  tutorial_type = st.sidebar.radio(
68
  "Choose a physics concept:",
69
+ ["1D Motion", "2D Projectile Motion", "Compare Motions"]
 
70
  )
71
 
72
+ if tutorial_type == "1D Motion":
73
+ show_1d_motion()
74
+ elif tutorial_type == "2D Projectile Motion":
75
+ show_2d_motion()
76
+ elif tutorial_type == "Compare Motions":
77
+ show_motion_comparison()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  def show_1d_motion():
80
  st.markdown('<div class="section-header">πŸ“ One-Dimensional Motion</div>', unsafe_allow_html=True)
 
474
 
475
  st.markdown("**Compare up to 3 different projectile motions side by side**")
476
 
477
+ # Create placeholders to prevent oscillation
478
+ plot_placeholder = st.empty()
479
+ table_placeholder = st.empty()
480
 
481
+ # Clear any previous matplotlib figures explicitly
482
+ plt.clf()
483
+ plt.close('all')
 
 
 
 
 
 
 
 
 
484
 
485
  # Create tabs for different trajectories
486
  tab1, tab2, tab3 = st.tabs(["πŸš€ Trajectory 1", "🎯 Trajectory 2", "⚽ Trajectory 3"])
487
 
488
  trajectories = []
489
 
490
+ # Use containers to isolate each tab's content
491
  with tab1:
492
  with st.container():
 
493
  col1, col2 = st.columns(2)
494
  with col1:
495
+ speed1 = st.slider("Speed 1 (m/s)", 5.0, 50.0, 20.0, key="comp_speed1")
496
+ angle1 = st.slider("Angle 1 (Β°)", 0.0, 90.0, 30.0, key="comp_angle1")
497
  with col2:
498
+ height1 = st.slider("Height 1 (m)", 0.0, 30.0, 0.0, key="comp_height1")
499
 
 
 
 
 
 
500
  motion1 = Motion2D(launch_speed=speed1, launch_angle=angle1, launch_height=height1)
501
  trajectories.append(("Trajectory 1", motion1, 'blue'))
502
 
 
503
  with tab2:
504
  with st.container():
 
505
  col1, col2 = st.columns(2)
506
  with col1:
507
+ speed2 = st.slider("Speed 2 (m/s)", 5.0, 50.0, 25.0, key="comp_speed2")
508
+ angle2 = st.slider("Angle 2 (Β°)", 0.0, 90.0, 45.0, key="comp_angle2")
509
  with col2:
510
+ height2 = st.slider("Height 2 (m)", 0.0, 30.0, 0.0, key="comp_height2")
511
 
 
 
 
 
 
512
  motion2 = Motion2D(launch_speed=speed2, launch_angle=angle2, launch_height=height2)
513
  trajectories.append(("Trajectory 2", motion2, 'red'))
514
 
 
515
  with tab3:
516
  with st.container():
 
517
  col1, col2 = st.columns(2)
518
  with col1:
519
+ speed3 = st.slider("Speed 3 (m/s)", 5.0, 50.0, 30.0, key="comp_speed3")
520
+ angle3 = st.slider("Angle 3 (Β°)", 0.0, 90.0, 60.0, key="comp_angle3")
521
  with col2:
522
+ height3 = st.slider("Height 3 (m)", 0.0, 30.0, 5.0, key="comp_height3")
523
 
 
 
 
 
 
524
  motion3 = Motion2D(launch_speed=speed3, launch_angle=angle3, launch_height=height3)
525
  trajectories.append(("Trajectory 3", motion3, 'green'))
526
 
527
+ # Update plot using placeholder (prevents oscillation)
528
+ with plot_placeholder.container():
 
 
 
 
529
  try:
530
+ plt.close('all')
531
  fig, ax = plt.subplots(figsize=(12, 8))
532
 
533
  max_range = 0
 
 
534
  for name, motion, color in trajectories:
535
  try:
536
  data = motion.trajectory_data(motion.calculate_flight_time())
537
  if len(data['x']) > 0:
538
  ax.plot(data['x'], data['y'], linewidth=3, label=name, color=color)
 
 
539
  ax.plot(motion.launch_x, motion.launch_height, 'o', markersize=8, color=color, alpha=0.7)
540
+ ax.plot(data['x'][-1], data['y'][-1], 's', markersize=8, color=color, alpha=0.7)
541
+ max_range = max(max_range, data['x'][-1] if data['x'][-1] > 0 else 0)
 
 
542
  except Exception as e:
543
+ st.error(f"Error plotting {name}: {str(e)}")
544
  continue
545
 
546
+ if max_range > 0:
 
547
  ax.set_xlabel('Horizontal Position (m)', fontsize=12)
548
  ax.set_ylabel('Vertical Position (m)', fontsize=12)
549
  ax.set_title('Trajectory Comparison', fontsize=16, fontweight='bold')
 
552
  ax.set_ylim(bottom=0)
553
  ax.set_xlim(0, max_range * 1.1)
554
 
 
555
  st.pyplot(fig, clear_figure=True)
556
  else:
557
  st.warning("No valid trajectory data to display")
 
559
  except Exception as e:
560
  st.error(f"Error creating comparison plot: {str(e)}")
561
  finally:
562
+ plt.close('all')
 
 
 
 
 
 
 
 
 
563
 
564
+ # Update table using placeholder (prevents oscillation)
565
+ with table_placeholder.container():
566
+ st.markdown("### πŸ“‹ Trajectory Comparison Table")
567
+
568
  try:
569
  comparison_data = []
570
  for name, motion, _ in trajectories:
 
580
  "Max Height (m)": f"{info['max_height']:.1f}"
581
  })
582
  except Exception as e:
 
583
  continue
584
 
585
  if comparison_data: