Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| import numpy as np | |
| import pandas as pd | |
| import plotly.graph_objects as go | |
| import plotly.subplots as sp | |
| from plotly.subplots import make_subplots | |
| from math import pi, radians, degrees | |
| import time | |
| # Set page configuration | |
| st.set_page_config( | |
| page_title="Trigonometry Basics Explorer", | |
| page_icon="📐", | |
| layout="wide" | |
| ) | |
| def safe_division(numerator, denominator, undefined_value=float('inf')): | |
| """Safely handle division by zero for trigonometric functions.""" | |
| return numerator / denominator if abs(denominator) > 1e-10 else undefined_value | |
| def calculate_trig_functions(angle_rad): | |
| """Calculate all six trigonometric functions for a given angle in radians.""" | |
| sin_val = np.sin(angle_rad) | |
| cos_val = np.cos(angle_rad) | |
| # Primary functions | |
| sine = sin_val | |
| cosine = cos_val | |
| tangent = safe_division(sin_val, cos_val) | |
| # Reciprocal functions | |
| cosecant = safe_division(1, sin_val) | |
| secant = safe_division(1, cos_val) | |
| cotangent = safe_division(cos_val, sin_val) | |
| return { | |
| 'sin': sine, | |
| 'cos': cosine, | |
| 'tan': tangent, | |
| 'csc': cosecant, | |
| 'sec': secant, | |
| 'cot': cotangent | |
| } | |
| def create_animated_unit_circle(angle_degrees, show_special_angles=True): | |
| """Create unit circle with Plotly - optimized version.""" | |
| # Convert angle to radians | |
| angle_rad = radians(angle_degrees) | |
| x_point = np.cos(angle_rad) | |
| y_point = np.sin(angle_rad) | |
| # Create figure with minimal traces | |
| fig = go.Figure() | |
| # Single trace for unit circle | |
| theta = np.linspace(0, 2*pi, 100) | |
| fig.add_trace(go.Scatter( | |
| x=np.cos(theta), | |
| y=np.sin(theta), | |
| mode='lines', | |
| line=dict(color='blue', width=2), | |
| name='Unit Circle', | |
| hoverinfo='skip' | |
| )) | |
| # Enhanced special angles display with radians | |
| if show_special_angles: | |
| # Major special angles with their radian equivalents | |
| special_angles_data = [ | |
| (0, "0°\n(0)"), | |
| (30, "30°\n(π/6)"), | |
| (45, "45°\n(π/4)"), | |
| (60, "60°\n(π/3)"), | |
| (90, "90°\n(π/2)"), | |
| (120, "120°\n(2π/3)"), | |
| (135, "135°\n(3π/4)"), | |
| (150, "150°\n(5π/6)"), | |
| (180, "180°\n(π)"), | |
| (210, "210°\n(7π/6)"), | |
| (225, "225°\n(5π/4)"), | |
| (240, "240°\n(4π/3)"), | |
| (270, "270°\n(3π/2)"), | |
| (300, "300°\n(5π/3)"), | |
| (315, "315°\n(7π/4)"), | |
| (330, "330°\n(11π/6)") | |
| ] | |
| special_x = [np.cos(radians(angle)) for angle, _ in special_angles_data] | |
| special_y = [np.sin(radians(angle)) for angle, _ in special_angles_data] | |
| special_labels = [label for _, label in special_angles_data] | |
| # Single trace for all special points with degree and radian labels | |
| fig.add_trace(go.Scatter( | |
| x=special_x, | |
| y=special_y, | |
| mode='markers+text', | |
| marker=dict(size=6, color='gray', opacity=0.8), | |
| text=special_labels, | |
| textposition="top center", | |
| textfont=dict(size=7), | |
| showlegend=False, | |
| name='Special Angles', | |
| hovertemplate='%{text}<extra></extra>' | |
| )) | |
| # Add arc to show the angle sweep from x-axis to current position | |
| if angle_degrees > 0: | |
| # Create arc from 0 to current angle | |
| arc_angles = np.linspace(0, angle_rad, max(10, int(angle_degrees/10))) | |
| arc_radius = 0.3 # Smaller radius for the arc | |
| arc_x = arc_radius * np.cos(arc_angles) | |
| arc_y = arc_radius * np.sin(arc_angles) | |
| fig.add_trace(go.Scatter( | |
| x=arc_x, | |
| y=arc_y, | |
| mode='lines', | |
| line=dict(color='orange', width=3), | |
| name=f'Angle Arc ({angle_degrees}°)', | |
| showlegend=False, | |
| hoverinfo='skip' | |
| )) | |
| # Add arrow at the end of the arc to show direction | |
| if len(arc_x) > 1: | |
| # Arrow direction | |
| dx = arc_x[-1] - arc_x[-2] | |
| dy = arc_y[-1] - arc_y[-2] | |
| fig.add_annotation( | |
| x=arc_x[-1], | |
| y=arc_y[-1], | |
| ax=arc_x[-1] - dx*5, | |
| ay=arc_y[-1] - dy*5, | |
| xref="x", | |
| yref="y", | |
| axref="x", | |
| ayref="y", | |
| arrowhead=2, | |
| arrowsize=1.5, | |
| arrowwidth=2, | |
| arrowcolor="orange", | |
| showarrow=True | |
| ) | |
| # Current point | |
| fig.add_trace(go.Scatter( | |
| x=[x_point], y=[y_point], | |
| mode='markers', | |
| marker=dict(size=10, color='red'), | |
| name=f'Point at {angle_degrees}°', | |
| hovertemplate=f'({x_point:.3f}, {y_point:.3f})<extra></extra>' | |
| )) | |
| # Radius line | |
| fig.add_trace(go.Scatter( | |
| x=[0, x_point], y=[0, y_point], | |
| mode='lines', | |
| line=dict(color='red', width=3), | |
| name=f'Radius', | |
| showlegend=False, | |
| hoverinfo='skip' | |
| )) | |
| # Coordinate lines | |
| fig.add_trace(go.Scatter( | |
| x=[x_point, x_point, None, 0, x_point], | |
| y=[0, y_point, None, 0, 0], | |
| mode='lines', | |
| line=dict(color='green', width=2, dash='dash'), | |
| name=f'sin={y_point:.3f}, cos={x_point:.3f}', | |
| hoverinfo='skip' | |
| )) | |
| # Optimized layout | |
| fig.update_layout( | |
| title=f'Unit Circle at {angle_degrees}° ({angle_rad:.3f} rad)', | |
| xaxis=dict( | |
| range=[-1.2, 1.2], | |
| scaleanchor="y", | |
| scaleratio=1, | |
| showgrid=True, | |
| zeroline=True | |
| ), | |
| yaxis=dict( | |
| range=[-1.2, 1.2], | |
| showgrid=True, | |
| zeroline=True | |
| ), | |
| showlegend=True, | |
| width=600, | |
| height=600, | |
| margin=dict(l=50, r=50, t=50, b=50) | |
| ) | |
| return fig | |
| def create_enhanced_function_plots(angle_min, angle_max, selected_functions_str, current_angle=None, show_special_angles=True): | |
| """Optimized function plots with single subplot per function.""" | |
| selected_functions = selected_functions_str.split(',') if selected_functions_str else [] | |
| if not selected_functions: | |
| return None | |
| # Use fewer points for better performance | |
| angle_range = np.linspace(angle_min, angle_max, 500) # Reduced from 1000 | |
| angle_rad = np.radians(angle_range) | |
| # Simple subplot layout | |
| num_functions = len(selected_functions) | |
| cols = min(2, num_functions) | |
| rows = (num_functions + cols - 1) // cols | |
| fig = make_subplots( | |
| rows=rows, cols=cols, | |
| subplot_titles=[f'{func.capitalize()} Function' for func in selected_functions] | |
| ) | |
| functions = { | |
| 'sin': {'func': np.sin, 'color': 'blue'}, | |
| 'cos': {'func': np.cos, 'color': 'red'}, | |
| 'tan': {'func': lambda x: np.clip(np.tan(x), -10, 10), 'color': 'green'}, # Clip for performance | |
| 'csc': {'func': lambda x: np.clip(1/np.sin(np.where(np.abs(np.sin(x)) > 0.01, x, np.nan)), -10, 10), 'color': 'purple'}, | |
| 'sec': {'func': lambda x: np.clip(1/np.cos(np.where(np.abs(np.cos(x)) > 0.01, x, np.nan)), -10, 10), 'color': 'orange'}, | |
| 'cot': {'func': lambda x: np.clip(1/np.tan(np.where(np.abs(np.tan(x)) > 0.01, x, np.nan)), -10, 10), 'color': 'brown'} | |
| } | |
| for idx, func_name in enumerate(selected_functions): | |
| row = idx // cols + 1 | |
| col = idx % cols + 1 | |
| if func_name in functions: | |
| func_info = functions[func_name] | |
| y_values = func_info['func'](angle_rad) | |
| # Main function plot | |
| fig.add_trace( | |
| go.Scatter( | |
| x=angle_range, | |
| y=y_values, | |
| mode='lines', | |
| line=dict(color=func_info['color'], width=2), | |
| name=func_name, | |
| showlegend=False | |
| ), | |
| row=row, col=col | |
| ) | |
| # Current angle marker (if provided) | |
| if current_angle is not None and angle_min <= current_angle <= angle_max: | |
| current_y = func_info['func'](radians(current_angle)) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=[current_angle], | |
| y=[current_y], | |
| mode='markers', | |
| marker=dict(size=8, color='red'), | |
| showlegend=False | |
| ), | |
| row=row, col=col | |
| ) | |
| # Simplified layout | |
| fig.update_layout( | |
| height=300 * rows, # Smaller height | |
| showlegend=False, | |
| margin=dict(l=50, r=50, t=50, b=50) | |
| ) | |
| return fig | |
| def create_comparison_table(angle_degrees): | |
| """Create an enhanced comparison table with more information.""" | |
| trig_values = calculate_trig_functions(radians(angle_degrees)) | |
| # Determine quadrant | |
| quadrant = "" | |
| if 0 <= angle_degrees < 90: | |
| quadrant = "I" | |
| elif 90 <= angle_degrees < 180: | |
| quadrant = "II" | |
| elif 180 <= angle_degrees < 270: | |
| quadrant = "III" | |
| else: | |
| quadrant = "IV" | |
| # Create enhanced dataframe | |
| values_df = pd.DataFrame({ | |
| 'Function': ['sin(θ)', 'cos(θ)', 'tan(θ)', 'csc(θ)', 'sec(θ)', 'cot(θ)'], | |
| 'Value': [ | |
| f"{trig_values['sin']:.4f}", | |
| f"{trig_values['cos']:.4f}", | |
| f"{trig_values['tan']:.4f}" if abs(trig_values['tan']) < 1000 else "undefined", | |
| f"{trig_values['csc']:.4f}" if abs(trig_values['csc']) < 1000 else "undefined", | |
| f"{trig_values['sec']:.4f}" if abs(trig_values['sec']) < 1000 else "undefined", | |
| f"{trig_values['cot']:.4f}" if abs(trig_values['cot']) < 1000 else "undefined" | |
| ], | |
| 'Sign': [ | |
| "+" if trig_values['sin'] >= 0 else "-", | |
| "+" if trig_values['cos'] >= 0 else "-", | |
| "+" if abs(trig_values['tan']) < 1000 and trig_values['tan'] >= 0 else "-" if abs(trig_values['tan']) < 1000 else "N/A", | |
| "+" if abs(trig_values['csc']) < 1000 and trig_values['csc'] >= 0 else "-" if abs(trig_values['csc']) < 1000 else "N/A", | |
| "+" if abs(trig_values['sec']) < 1000 and trig_values['sec'] >= 0 else "-" if abs(trig_values['sec']) < 1000 else "N/A", | |
| "+" if abs(trig_values['cot']) < 1000 and trig_values['cot'] >= 0 else "-" if abs(trig_values['cot']) < 1000 else "N/A" | |
| ], | |
| 'Definition': [ | |
| 'y-coordinate / opposite', | |
| 'x-coordinate / adjacent', | |
| 'sin(θ)/cos(θ) = opp/adj', | |
| '1/sin(θ) = hyp/opp', | |
| '1/cos(θ) = hyp/adj', | |
| 'cos(θ)/sin(θ) = adj/opp' | |
| ] | |
| }) | |
| return values_df, quadrant | |
| def main(): | |
| st.title("📐 Trigonometry Basics Explorer") | |
| st.markdown("### Learn the Six Basic Trigonometric Functions Interactively!") | |
| # Stable tip selection using session state | |
| if 'current_tip' not in st.session_state: | |
| tips = [ | |
| "💡 **Tip**: Remember SOHCAHTOA - Sine=Opposite/Hypotenuse, Cosine=Adjacent/Hypotenuse, Tangent=Opposite/Adjacent", | |
| "🎯 **Did you know?**: The word 'sine' comes from the Latin word 'sinus' meaning 'bay' or 'fold'", | |
| "🔄 **Pattern**: Notice how sine and cosine are just shifted versions of each other!", | |
| "📊 **Memory trick**: In Quadrant I, all functions are positive. Use 'All Students Take Calculus' for Q1,Q2,Q3,Q4", | |
| "🌊 **Cool fact**: Trigonometric functions model waves, from sound waves to ocean tides!" | |
| ] | |
| st.session_state.current_tip = np.random.choice(tips) | |
| # Add a button to change tip if desired | |
| col_tip, col_button = st.columns([4, 1]) | |
| with col_tip: | |
| st.info(st.session_state.current_tip) | |
| with col_button: | |
| if st.button("💡 New Tip", key="new_tip_button"): | |
| tips = [ | |
| "💡 **Tip**: Remember SOHCAHTOA - Sine=Opposite/Hypotenuse, Cosine=Adjacent/Hypotenuse, Tangent=Opposite/Adjacent", | |
| "🎯 **Did you know?**: The word 'sine' comes from the Latin word 'sinus' meaning 'bay' or 'fold'", | |
| "🔄 **Pattern**: Notice how sine and cosine are just shifted versions of each other!", | |
| "📊 **Memory trick**: In Quadrant I, all functions are positive. Use 'All Students Take Calculus' for Q1,Q2,Q3,Q4", | |
| "🌊 **Cool fact**: Trigonometric functions model waves, from sound waves to ocean tides!" | |
| ] | |
| st.session_state.current_tip = np.random.choice(tips) | |
| st.rerun() | |
| # Sidebar controls | |
| st.sidebar.header("🎛️ Controls") | |
| # Enhanced function selection with unique keys | |
| st.sidebar.subheader("📊 Function Plots") | |
| col1, col2 = st.sidebar.columns(2) | |
| with col1: | |
| show_primary = st.checkbox("Primary Functions", value=True, key="show_primary_funcs") | |
| with col2: | |
| show_reciprocal = st.checkbox("Reciprocal Functions", value=False, key="show_reciprocal_funcs") | |
| selected_functions = [] | |
| if show_primary: | |
| selected_functions.extend(['sin', 'cos', 'tan']) | |
| if show_reciprocal: | |
| selected_functions.extend(['csc', 'sec', 'cot']) | |
| # Plot options with unique keys | |
| plot_range = st.sidebar.slider("Plot range (degrees)", 180, 720, 360, 90, key="plot_range_slider") | |
| show_special_angles = st.sidebar.checkbox("Show special angle markers", value=True, key="show_special_angles_cb") | |
| # Enhanced angle input with unique keys | |
| angle_input_method = st.sidebar.radio( | |
| "Choose angle input method:", | |
| ["Slider", "Text Input", "Common Angles"], | |
| key="angle_input_method_radio" | |
| ) | |
| # Settings with unique keys | |
| st.sidebar.subheader("🔧 Settings") | |
| angle_unit = st.sidebar.radio("Angle Units:", ["Degrees", "Radians"], key="angle_unit_radio") | |
| # Modify angle input methods to support both units with unique keys | |
| if angle_input_method == "Slider": | |
| if angle_unit == "Degrees": | |
| angle = st.sidebar.slider("Angle (degrees)", 0, 360, 45, 5, key="angle_degrees_slider") | |
| else: | |
| angle_rad = st.sidebar.slider("Angle (radians)", 0.0, 2*pi, pi/4, 0.1, key="angle_radians_slider") | |
| angle = degrees(angle_rad) | |
| elif angle_input_method == "Text Input": | |
| if angle_unit == "Degrees": | |
| angle = st.sidebar.number_input("Angle (degrees)", value=45.0, step=1.0, min_value=0.0, max_value=360.0, key="angle_degrees_input") | |
| else: | |
| angle_rad = st.sidebar.number_input("Angle (radians)", value=pi/4, step=0.1, min_value=0.0, max_value=2*pi, key="angle_radians_input") | |
| angle = degrees(angle_rad) | |
| else: # Common Angles | |
| common_angles = { | |
| "0°": 0, "30°": 30, "45°": 45, "60°": 60, "90°": 90, | |
| "120°": 120, "135°": 135, "150°": 150, "180°": 180, | |
| "210°": 210, "225°": 225, "240°": 240, "270°": 270, | |
| "300°": 300, "315°": 315, "330°": 330, "360°": 360 | |
| } | |
| selected_angle = st.sidebar.selectbox("Select common angle:", list(common_angles.keys()), key="common_angles_selectbox") | |
| angle = common_angles[selected_angle] | |
| # Main content area - optimized | |
| col1, col2 = st.columns([1, 1]) | |
| with col1: | |
| st.subheader("🔄 Enhanced Unit Circle") | |
| # Fixed parameter name | |
| fig_circle = create_animated_unit_circle(angle, show_special_angles) | |
| st.plotly_chart(fig_circle, use_container_width=True, config={'displayModeBar': False}) | |
| # Enhanced values table | |
| values_df, quadrant = create_comparison_table(angle) | |
| st.subheader(f"📊 Function Values (Quadrant {quadrant})") | |
| st.dataframe(values_df, use_container_width=True) | |
| st.info(f"**Angle {angle}° is in Quadrant {quadrant}**") | |
| with col2: | |
| if selected_functions: | |
| st.subheader("📈 Enhanced Function Graphs") | |
| # Convert list to string for caching | |
| selected_functions_str = ','.join(selected_functions) | |
| fig_functions = create_enhanced_function_plots( | |
| -plot_range//2, plot_range//2, | |
| selected_functions_str, | |
| angle, | |
| show_special_angles | |
| ) | |
| if fig_functions: | |
| st.plotly_chart(fig_functions, use_container_width=True, config={'displayModeBar': False}) | |
| else: | |
| st.info("Select function types from the sidebar to see their graphs.") | |
| # Simplified calculator | |
| st.subheader("🧮 Quick Calculator") | |
| calc_angle = st.number_input("Calculate for angle:", value=float(angle), step=1.0, key="calc_angle_input") | |
| if st.button("Calculate", key="calc_button"): | |
| calc_values = calculate_trig_functions(radians(calc_angle)) | |
| col_calc1, col_calc2 = st.columns(2) | |
| with col_calc1: | |
| st.metric("sin", f"{calc_values['sin']:.4f}") | |
| st.metric("cos", f"{calc_values['cos']:.4f}") | |
| st.metric("tan", f"{calc_values['tan']:.4f}" if abs(calc_values['tan']) < 1000 else "undefined") | |
| with col_calc2: | |
| st.metric("csc", f"{calc_values['csc']:.4f}" if abs(calc_values['csc']) < 1000 else "undefined") | |
| st.metric("sec", f"{calc_values['sec']:.4f}" if abs(calc_values['sec']) < 1000 else "undefined") | |
| st.metric("cot", f"{calc_values['cot']:.4f}" if abs(calc_values['cot']) < 1000 else "undefined") | |
| # Educational content (unchanged) | |
| st.markdown("---") | |
| st.subheader("📚 Understanding Trigonometric Functions") | |
| tab1, tab2, tab3, tab4, tab5 = st.tabs(["Definitions", "Relationships", "Key Angles", "Patterns", "Applications"]) | |
| with tab1: | |
| st.markdown(""" | |
| **Primary Functions:** | |
| - **Sine (sin)**: The y-coordinate of a point on the unit circle | |
| - **Cosine (cos)**: The x-coordinate of a point on the unit circle | |
| - **Tangent (tan)**: The ratio sin/cos, representing the slope of the radius line | |
| **Reciprocal Functions:** | |
| - **Cosecant (csc)**: 1/sin, reciprocal of sine | |
| - **Secant (sec)**: 1/cos, reciprocal of cosine | |
| - **Cotangent (cot)**: 1/tan or cos/sin, reciprocal of tangent | |
| """) | |
| with tab2: | |
| st.markdown(""" | |
| **Fundamental Identity:** | |
| - sin²(θ) + cos²(θ) = 1 | |
| **Quotient Identities:** | |
| - tan(θ) = sin(θ)/cos(θ) | |
| - cot(θ) = cos(θ)/sin(θ) | |
| **Reciprocal Identities:** | |
| - csc(θ) = 1/sin(θ) | |
| - sec(θ) = 1/cos(θ) | |
| - cot(θ) = 1/tan(θ) | |
| """) | |
| with tab3: | |
| key_angles_df = pd.DataFrame({ | |
| 'Angle': ['0°', '30°', '45°', '60°', '90°', '120°', '135°', '150°', '180°', '270°', '360°'], | |
| 'sin': ['0', '1/2', '√2/2', '√3/2', '1', '√3/2', '√2/2', '1/2', '0', '-1', '0'], | |
| 'cos': ['1', '√3/2', '√2/2', '1/2', '0', '-1/2', '-√2/2', '-√3/2', '-1', '0', '1'], | |
| 'tan': ['0', '√3/3', '1', '√3', 'undefined', '-√3', '-1', '-√3/3', '0', 'undefined', '0'] | |
| }) | |
| st.dataframe(key_angles_df, use_container_width=True) | |
| st.subheader("Radian Equivalents") | |
| radian_df = pd.DataFrame({ | |
| 'Degrees': ['0°', '30°', '45°', '60°', '90°', '180°', '270°', '360°'], | |
| 'Radians': ['0', 'π/6', 'π/4', 'π/3', 'π/2', 'π', '3π/2', '2π'], | |
| 'Decimal': ['0', '0.524', '0.785', '1.047', '1.571', '3.142', '4.712', '6.283'] | |
| }) | |
| st.dataframe(radian_df, use_container_width=True) | |
| with tab4: | |
| st.markdown(""" | |
| **Sign Patterns by Quadrant:** | |
| - **Quadrant I (0° to 90°)**: All functions positive | |
| - **Quadrant II (90° to 180°)**: Only sine positive | |
| - **Quadrant III (180° to 270°)**: Only tangent positive | |
| - **Quadrant IV (270° to 360°)**: Only cosine positive | |
| **Remember**: "All Students Take Calculus" (All, Sin, Tan, Cos) | |
| """) | |
| with tab5: | |
| st.markdown(""" | |
| **Real-world Applications:** | |
| - **Physics**: Wave motion, oscillations, circular motion | |
| - **Engineering**: Signal processing, electrical circuits | |
| - **Navigation**: GPS systems, celestial navigation | |
| - **Computer Graphics**: Rotations, animations | |
| - **Music**: Sound waves, harmonics | |
| - **Architecture**: Designing arches and domes | |
| """) | |
| if __name__ == "__main__": | |
| main() |