├── 12320885 int375(ansari).docx ├── README.md └── project codes.py /12320885 int375(ansari).docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ansari12321/EarthShake-30-Days-of-Big-Quakes-/HEAD/12320885 int375(ansari).docx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EarthShake-30-Days-of-Big-Quakes- 2 | EarthShake is a data-driven project that visualizes and analyzes seismic activity over a 30-day period, focusing on major earthquakes around the globe. Using real-time data from the USGS (United States Geological Survey), this project explores patterns in magnitude, depth, and geographic distribution of significant quakes. 3 | -------------------------------------------------------------------------------- /project codes.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import plotly.express as px 3 | import plotly.graph_objects as go 4 | from dash import Dash, dcc, html, Input, Output, State 5 | from dash.exceptions import PreventUpdate 6 | import numpy as np 7 | import seaborn as sns 8 | import statsmodels.api as sm 9 | from matplotlib.colors import LinearSegmentedColormap 10 | 11 | # Load and preprocess data 12 | try: 13 | a df = pd.read_csv(r"C:\Users\adars\OneDrive\Desktop\jupyter projects\earthquake_data.csv") 14 | print("Dataset loaded successfully. Shape:", df.shape) 15 | print("Sample of latitude column:", df['latitude'].head().tolist()) 16 | if df.empty or df['latitude'].isna().all(): 17 | print("Error: DataFrame is empty or 'latitude' column contains only NaN values.") 18 | exit(1) 19 | except FileNotFoundError: 20 | print("Error: Dataset not found at specified path. Please check the file path.") 21 | exit(1) 22 | except Exception as e: 23 | print(f"Error loading dataset: {e}") 24 | exit(1) 25 | 26 | # Preprocessing 27 | df['Station Count'] = df['Station Count'].fillna(0) 28 | df['Azimuth Gap'] = df['Azimuth Gap'].fillna(0) 29 | df['Distance'] = df['Distance'].fillna(0) 30 | df['RMS'] = df['RMS'].fillna(0) 31 | df['horizontalError'] = df['horizontalError'].fillna(0) 32 | df['magError'] = df['magError'].fillna(0) 33 | df['magNst'] = df['magNst'].fillna(0) 34 | df['time'] = pd.to_datetime(df['time'], format='ISO8601', utc=True, errors='coerce') 35 | if df['time'].isna().any(): 36 | print("Warning: Some 'time' values could not be parsed. Rows with invalid 'time' will be dropped.") 37 | df = df.dropna(subset=['time']) 38 | print("Time column parsed. Shape after time drop:", df.shape) 39 | df['region'] = df['place'].str.extract(r',\s*(.*)').fillna('Unknown') 40 | df['Magnitude_Category'] = df['mag'].apply( 41 | lambda x: 'Below 2.5' if x < 2.5 else ('2.5 - 4.5' if x <= 4.5 else 'Above 4.5') 42 | ) 43 | 44 | # Check and create bins only if latitude has valid data 45 | if not df['latitude'].empty and not df['latitude'].isna().all(): 46 | df['lat_bin'] = pd.cut(df['latitude'], bins=20, labels=False) 47 | df['lon_bin'] = pd.cut(df['longitude'], bins=20, labels=False) 48 | else: 49 | print("Error: 'latitude' or 'longitude' column is empty or contains only NaN values. Binning skipped.") 50 | exit(1) 51 | 52 | # Initialize Dash app 53 | app = Dash(__name__) 54 | 55 | # Gradient background style 56 | app.layout = html.Div([ 57 | # Header with logo and title moved to left 58 | html.Div([ 59 | html.Img(src="/assets/planet-earth.png", 60 | style={'verticalAlign': 'middle', 'marginRight': '15px', 'width': '60px', 'height': '60px'}), 61 | html.H1("Earthquake Analytics Dashboard", style={ 62 | 'color': '#FF6F61', 'fontSize': '48px', 'padding': '15px 0', 'textShadow': '3px 3px 6px #000', 63 | 'fontWeight': 'bold', 'display': 'inline-block', 'letterSpacing': '1px' 64 | }) 65 | ], style={ 66 | 'background': 'linear-gradient(135deg, #1a1a1a, #2a2a2a)', 'borderBottom': '4px solid #FF6F61', 67 | 'display': 'flex', 'alignItems': 'center', 'padding': '10px 20px', 'justifyContent': 'flex-start' 68 | }), 69 | 70 | # Main layout with sidebar and content 71 | html.Div([ 72 | # Sidebar 73 | html.Div([ 74 | html.H3("Controls", style={'color': '#FF8E53', 'fontSize': '26px', 'marginBottom': '25px', 'textShadow': '1px 1px 3px #000'}), 75 | html.Label("Select Tab:", style={'color': '#FFCC70', 'fontSize': '16px'}), 76 | dcc.RadioItems( 77 | id='tab-selector', 78 | options=[ 79 | {'label': 'Overview', 'value': 'overview'}, 80 | {'label': 'Depth & Magnitude', 'value': 'depth-mag'}, 81 | {'label': 'Regional Analysis', 'value': 'regional'}, 82 | {'label': 'Magnitude Breakdown', 'value': 'mag-breakdown'} 83 | ], 84 | value='overview', 85 | style={'color': '#FFCC70', 'marginBottom': '30px', 'fontSize': '14px'} 86 | ), 87 | html.Label("Time Range:", style={'color': '#FFCC70', 'fontSize': '16px'}), 88 | dcc.DatePickerRange( 89 | id='date-picker', 90 | min_date_allowed=df['time'].min().date(), 91 | max_date_allowed=df['time'].max().date(), 92 | start_date=df['time'].min().date(), 93 | end_date=df['time'].max().date(), 94 | style={'marginBottom': '30px', 'width': '100%', 'fontSize': '14px'} 95 | ), 96 | html.Label("Magnitude Range:", style={'color': '#FFCC70', 'fontSize': '16px'}), 97 | dcc.RangeSlider( 98 | id='mag-slider', 99 | min=df['mag'].min(), 100 | max=df['mag'].max(), 101 | step=0.1, 102 | value=[df['mag'].min(), df['mag'].max()], 103 | marks={i: str(i) for i in range(int(df['mag'].min()), int(df['mag'].max()) + 1)}, 104 | tooltip={"placement": "bottom", "always_visible": True}, 105 | updatemode='drag' 106 | ), 107 | html.Label("Region:", style={'color': '#FFCC70', 'fontSize': '16px'}), 108 | dcc.Dropdown( 109 | id='region-dropdown', 110 | options=[{'label': 'All', 'value': 'All'}] + [ 111 | {'label': region, 'value': region} for region in sorted(df['region'].unique()) 112 | ], 113 | value='All', 114 | multi=True, 115 | style={'marginBottom': '30px', 'fontSize': '14px', 'color': '#000000', 'backgroundColor': '#2a2a2a', 'border': '1px solid #FF6F61', 'borderRadius': '5px'} 116 | ), 117 | html.Label("Depth Range:", style={'color': '#FFCC70', 'fontSize': '16px'}), 118 | dcc.RangeSlider( 119 | id='depth-slider', 120 | min=df['depth'].min(), 121 | max=df['depth'].max(), 122 | step=1, 123 | value=[df['depth'].min(), df['depth'].max()], 124 | marks={i: str(i) for i in range(int(df['depth'].min()), int(df['depth'].max()) + 1, 100)}, 125 | tooltip={"placement": "bottom", "always_visible": True}, 126 | updatemode='drag' 127 | ), 128 | html.Button("Reset Filters", id="btn-reset", style={ 129 | 'background': 'linear-gradient(45deg, #FF6F61, #FF8E53)', 'color': '#000000', 'border': 'none', 'padding': '15px 30px', 130 | 'cursor': 'pointer', 'marginTop': '30px', 'width': '100%', 'fontSize': '18px', 'borderRadius': '10px', 'boxShadow': '2px 2px 5px #000' 131 | }), 132 | html.Button("Download Data", id="btn-download", style={ 133 | 'background': 'linear-gradient(45deg, #FF6F61, #FF8E53)', 'color': '#000000', 'border': 'none', 'padding': '15px 30px', 134 | 'cursor': 'pointer', 'marginTop': '20px', 'width': '100%', 'fontSize': '18px', 'borderRadius': '10px', 'boxShadow': '2px 2px 5px #000' 135 | }), 136 | dcc.Download(id="download-data"), 137 | html.Div([ 138 | html.P("Developed by: Adarsh Kumar", style={'color': '#FFCC70', 'fontSize': '16px', 'marginTop': '30px', 'textAlign': 'center', 'fontWeight': 'bold'}), 139 | html.P("Data Science Student | Aspiring Data Analyst", style={'color': '#FFCC70', 'fontSize': '14px', 'textAlign': 'center'}), 140 | html.P("Email: adarshsingh6534@gmail.com", style={'color': '#FFCC70', 'fontSize': '14px', 'textAlign': 'center'}) 141 | ], style={'padding': '20px', 'borderTop': '1px solid #FF6F61', 'marginTop': '40px', 'textAlign': 'center'}) 142 | ], style={ 143 | 'width': '25%', 'background': 'linear-gradient(135deg, #2a2a2a, #3a3a3a)', 'padding': '30px', 'height': '100vh', 144 | 'color': 'white', 'overflowY': 'auto', 'borderRight': '3px solid #FF6F61', 'boxShadow': '5px 0 10px rgba(0,0,0,0.5)' 145 | }), 146 | 147 | # Main content with dynamic layout and graph selector 148 | html.Div([ 149 | dcc.Dropdown( 150 | id='graph-selector-dropdown', 151 | options=[ 152 | {'label': 'None', 'value': 'none'}, 153 | {'label': '3D Depth Visualization', 'value': '3d', 'disabled': True}, 154 | {'label': 'High-Risk Zones', 'value': 'risk', 'disabled': True} 155 | ], 156 | value='none', 157 | style={'width': '200px', 'marginBottom': '20px', 'fontSize': '16px', 'color': '#000000', 'background': 'linear-gradient(45deg, #FF6F61, #FF8E53)', 'border': '1px solid #FF6F61', 'borderRadius': '5px', 'display': 'none'} 158 | ), 159 | html.Div(id='tab-content', style={'padding': '30px'}) 160 | ], style={'width': '75%', 'background': 'linear-gradient(135deg, #121212, #1e1e1e)', 'minHeight': '100vh'}) 161 | ], style={'display': 'flex', 'flexDirection': 'row', 'animation': 'fadeIn 1s'}) 162 | ], style={'background': 'linear-gradient(135deg, #1a1a1a, #2a2a2a, #3a3a3a)', 'minHeight': '100vh'}) 163 | 164 | # Animation CSS 165 | app.css.append_css({ 166 | 'external_url': 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' 167 | }) 168 | 169 | # Custom colormap from EDA 170 | custom_colors = ['#f2f2f2', '#b87333', '#d2691e', '#ff7f50', '#dda0dd', '#8b0000', '#000000'] 171 | custom_cmap = LinearSegmentedColormap.from_list("custom_palette", custom_colors, N=256) 172 | 173 | # Helper functions for plots 174 | def create_hotspot_map(filtered_df): 175 | if filtered_df.empty: 176 | return go.Figure() 177 | sampled_df = filtered_df.sample(n=min(1000, len(filtered_df)), random_state=42) if len(filtered_df) > 1000 else filtered_df 178 | fig = px.scatter_geo( 179 | sampled_df, 180 | lat='latitude', 181 | lon='longitude', 182 | color='mag', 183 | size='mag', 184 | hover_name='place', 185 | hover_data={'mag': True, 'latitude': True, 'longitude': True, 'time': True}, 186 | projection='natural earth', 187 | title='Global Earthquake Hotspots', 188 | color_continuous_scale='OrRd', 189 | template='plotly_dark' 190 | ) 191 | fig.update_layout( 192 | title_font=dict(size=28, color='#FF6F61'), 193 | title_x=0.5, 194 | margin=dict(l=0, r=0, t=60, b=0), 195 | height=600, 196 | showlegend=True 197 | ) 198 | return fig 199 | 200 | def create_magnitude_trends(filtered_df): 201 | if filtered_df.empty: 202 | return go.Figure() 203 | filtered_df = filtered_df.sort_values(by='time') 204 | sampled_df = filtered_df.sample(n=min(1000, len(filtered_df)), random_state=42) if len(filtered_df) > 1000 else filtered_df 205 | fig = px.scatter( 206 | sampled_df, 207 | x='time', 208 | y='mag', 209 | color='mag', 210 | color_continuous_scale='Turbo', 211 | hover_data=['place', 'mag', 'latitude', 'longitude', 'time'], 212 | title='Earthquake Magnitude Trends Over Time' 213 | ) 214 | fig.update_layout( 215 | template='plotly_dark', 216 | title_font=dict(size=24, color='#FF8E53'), 217 | xaxis_title='Date', 218 | yaxis_title='Magnitude', 219 | title_x=0.5, 220 | margin=dict(l=50, r=50, t=60, b=50), 221 | height=500 222 | ) 223 | return fig 224 | 225 | def create_depth_histogram(filtered_df): 226 | if filtered_df.empty: 227 | return go.Figure() 228 | fig = go.Figure() 229 | fig.add_trace(go.Histogram(x=filtered_df['depth'], nbinsx=30, marker_color='#00CED1', opacity=0.8)) 230 | fig.update_layout( 231 | template='plotly_dark', 232 | title='Distribution of Earthquake Depths', 233 | title_font=dict(size=22, color='#FF8E53'), 234 | xaxis_title='Depth (km)', 235 | yaxis_title='Frequency', 236 | title_x=0.5, 237 | margin=dict(l=50, r=50, t=60, b=50), 238 | height=500 239 | ) 240 | return fig 241 | 242 | def create_mag_depth_scatter(filtered_df): 243 | if filtered_df.empty: 244 | return go.Figure() 245 | sampled_df = filtered_df.sample(n=min(1000, len(filtered_df)), random_state=42) if len(filtered_df) > 1000 else filtered_df 246 | fig = px.scatter( 247 | sampled_df, 248 | x='depth', 249 | y='mag', 250 | color='mag', 251 | color_continuous_scale='plasma', 252 | hover_data=['place', 'mag', 'depth'], 253 | title='Magnitude vs Depth of Earthquakes' 254 | ) 255 | fig.update_layout( 256 | template='plotly_dark', 257 | title_font=dict(size=22, color='#FF6F61'), 258 | xaxis_title='Depth (km)', 259 | yaxis_title='Magnitude', 260 | title_x=0.5, 261 | margin=dict(l=50, r=50, t=60, b=50), 262 | height=500 263 | ) 264 | return fig 265 | 266 | def create_3d_depth_plot(filtered_df): 267 | if filtered_df.empty: 268 | return go.Figure() 269 | sampled_df = filtered_df.sample(n=min(500, len(filtered_df)), random_state=42) if len(filtered_df) > 500 else filtered_df 270 | fig = go.Figure(data=[ 271 | go.Scatter3d( 272 | x=sampled_df['longitude'], 273 | y=sampled_df['latitude'], 274 | z=sampled_df['depth'] * -1, 275 | mode='markers', 276 | marker=dict(size=5, color=sampled_df['mag'], colorscale='Viridis', showscale=True), 277 | text=sampled_df['place'], 278 | hoverinfo='text+x+y+z' 279 | ) 280 | ]) 281 | fig.update_layout( 282 | template='plotly_dark', 283 | title='3D Earthquake Depth Visualization', 284 | title_font=dict(size=22, color='#FF6F61'), 285 | title_x=0.5, 286 | scene=dict( 287 | xaxis_title='Longitude', 288 | yaxis_title='Latitude', 289 | zaxis_title='Depth (km)', 290 | xaxis=dict(color='white'), 291 | yaxis=dict(color='white'), 292 | zaxis=dict(color='white') 293 | ), 294 | margin=dict(l=0, r=0, t=60, b=0), 295 | height=500 296 | ) 297 | return fig 298 | 299 | def create_region_frequency(filtered_df): 300 | if filtered_df.empty: 301 | return go.Figure() 302 | top_regions = filtered_df['region'].value_counts().head(10).reset_index() 303 | top_regions.columns = ['region', 'count'] 304 | fig = px.bar( 305 | top_regions, 306 | x='count', 307 | y='region', 308 | orientation='h', 309 | title='Top 10 Regions by Earthquake Frequency', 310 | color='count', 311 | color_continuous_scale='magma' 312 | ) 313 | fig.update_layout( 314 | template='plotly_dark', 315 | title_font=dict(size=22, color='#FF8E53'), 316 | xaxis_title='Number of Earthquakes', 317 | yaxis_title='Region', 318 | title_x=0.5, 319 | margin=dict(l=200, r=50, t=60, b=50), 320 | height=500 321 | ) 322 | return fig 323 | 324 | def create_region_avg_magnitude(filtered_df): 325 | if filtered_df.empty: 326 | return go.Figure() 327 | avg_mag_by_region = filtered_df.groupby('region')['mag'].mean().sort_values(ascending=False).head(10).reset_index() 328 | fig = px.bar( 329 | avg_mag_by_region, 330 | x='mag', 331 | y='region', 332 | orientation='h', 333 | title='Top 10 Regions with Highest Average Magnitude', 334 | color='mag', 335 | color_continuous_scale='plasma' 336 | ) 337 | fig.update_layout( 338 | template='plotly_dark', 339 | title_font=dict(size=22, color='#FF6F61'), 340 | xaxis_title='Average Magnitude', 341 | yaxis_title='Region', 342 | title_x=0.5, 343 | margin=dict(l=200, r=50, t=60, b=50), 344 | height=500 345 | ) 346 | return fig 347 | 348 | def create_risk_zones(filtered_df): 349 | if filtered_df.empty: 350 | return go.Figure() 351 | risk_df = filtered_df.groupby('region').agg({ 352 | 'id': 'count', 353 | 'mag': 'mean' 354 | }).rename(columns={'id': 'quake_count', 'mag': 'avg_magnitude'}).reset_index() 355 | risk_df['risk_score'] = risk_df['quake_count'] * risk_df['avg_magnitude'] 356 | risk_df = risk_df.sort_values(by='risk_score', ascending=False).head(15) 357 | fig = px.scatter( 358 | risk_df, 359 | x='quake_count', 360 | y='avg_magnitude', 361 | size='risk_score', 362 | color='region', 363 | hover_data=['risk_score'], 364 | title='High-Risk Earthquake Zones (Top 15)' 365 | ) 366 | fig.update_layout( 367 | template='plotly_dark', 368 | title_font=dict(size=22, color='#FF6F61'), 369 | xaxis_title='Number of Earthquakes', 370 | yaxis_title='Average Magnitude', 371 | title_x=0.5, 372 | margin=dict(l=50, r=50, t=60, b=50), 373 | height=500 374 | ) 375 | return fig 376 | 377 | def create_magnitude_category(filtered_df): 378 | if filtered_df.empty: 379 | return go.Figure() 380 | freq_data = filtered_df['Magnitude_Category'].value_counts().sort_index().reset_index() 381 | freq_data.columns = ['Category', 'Count'] 382 | fig = px.bar( 383 | freq_data, 384 | x='Count', 385 | y='Category', 386 | orientation='h', 387 | title='Earthquake Frequency by Magnitude Category', 388 | color='Category', 389 | color_discrete_sequence=['#FF6F61', '#FF8E53', '#FFCC70'] 390 | ) 391 | fig.update_layout( 392 | template='plotly_dark', 393 | title_font=dict(size=20, color='#FFCC70'), 394 | xaxis_title='Number of Earthquakes', 395 | yaxis_title='Magnitude Category', 396 | title_x=0.5, 397 | margin=dict(l=200, r=50, t=60, b=50), 398 | height=500 399 | ) 400 | return fig 401 | 402 | def create_magnitude_depth_time(filtered_df): 403 | if filtered_df.empty: 404 | return go.Figure() 405 | sampled_df = filtered_df.sample(n=min(1000, len(filtered_df)), random_state=42) if len(filtered_df) > 1000 else filtered_df 406 | fig = px.scatter( 407 | sampled_df, 408 | x='depth', 409 | y='mag', 410 | animation_frame=sampled_df['time'].dt.strftime('%Y-%m-%d'), 411 | animation_group='place', 412 | color='mag', 413 | size='mag', 414 | hover_data=['place', 'time'], 415 | title='Magnitude and Depth Over Time (Animated)', 416 | color_continuous_scale='Viridis' 417 | ) 418 | fig.update_layout( 419 | template='plotly_dark', 420 | title_font=dict(size=20, color='#FFCC70'), 421 | xaxis_title='Depth (km)', 422 | yaxis_title='Magnitude', 423 | title_x=0.5, 424 | margin=dict(l=50, r=50, t=60, b=50), 425 | height=500 426 | ) 427 | fig.layout.updatemenus[0].buttons[0].args[1]['frame']['duration'] = 300 428 | fig.layout.updatemenus[0].buttons[0].args[1]['transition']['duration'] = 200 429 | return fig 430 | 431 | # Callback for tab content and graph selector visibility 432 | @app.callback( 433 | [Output('tab-content', 'children'), 434 | Output('graph-selector-dropdown', 'style'), 435 | Output('graph-selector-dropdown', 'options'), 436 | Output('graph-selector-dropdown', 'value')], 437 | [Input('tab-selector', 'value'), 438 | Input('date-picker', 'start_date'), 439 | Input('date-picker', 'end_date'), 440 | Input('mag-slider', 'value'), 441 | Input('region-dropdown', 'value'), 442 | Input('depth-slider', 'value'), 443 | Input('graph-selector-dropdown', 'value')] 444 | ) 445 | def update_tab(tab, start_date, end_date, mag_range, regions, depth_range, graph_select): 446 | if not all([start_date, end_date, mag_range, depth_range]): 447 | return (html.Div(["Error: Missing input data"], style={'color': 'red', 'fontSize': '18px', 'textAlign': 'center'}), 448 | {'display': 'none'}, [{'label': 'None', 'value': 'none'}], 'none') 449 | 450 | start_date = pd.to_datetime(start_date).tz_localize('UTC') 451 | end_date = pd.to_datetime(end_date).tz_localize('UTC') 452 | print(f"Start Date: {start_date}, End Date: {end_date}, Types: {type(start_date)}, {type(end_date)}") 453 | 454 | filtered_df = df.copy() 455 | try: 456 | filtered_df = filtered_df[ 457 | (filtered_df['time'] >= start_date) & 458 | (filtered_df['time'] <= end_date) & 459 | (filtered_df['mag'] >= mag_range[0]) & 460 | (filtered_df['mag'] <= mag_range[1]) & 461 | (filtered_df['depth'] >= depth_range[0]) & 462 | (filtered_df['depth'] <= depth_range[1]) 463 | ] 464 | if regions and regions != ['All']: 465 | if isinstance(regions, str): 466 | regions = [regions] 467 | filtered_df = filtered_df[filtered_df['region'].isin(regions)] 468 | elif regions == ['All']: 469 | pass 470 | print(f"Filtered data shape: {filtered_df.shape}, Time dtype: {filtered_df['time'].dtype}") 471 | if filtered_df.empty: 472 | return (html.Div(["No data available for selected filters"], style={'color': 'white', 'fontSize': '18px', 'textAlign': 'center'}), 473 | {'display': 'none'}, [{'label': 'None', 'value': 'none'}], 'none') 474 | except Exception as e: 475 | return (html.Div([f"Error filtering data: {str(e)}"], style={'color': 'red', 'fontSize': '18px', 'textAlign': 'center'}), 476 | {'display': 'none'}, [{'label': 'None', 'value': 'none'}], 'none') 477 | 478 | selector_style = {'width': '200px', 'marginBottom': '20px', 'fontSize': '16px', 'color': '#000000', 'background': 'linear-gradient(45deg, #FF6F61, #FF8E53)', 'border': '1px solid #FF6F61', 'borderRadius': '5px', 'display': 'none'} 479 | selector_options = [{'label': 'None', 'value': 'none'}] 480 | 481 | if tab in ['depth-mag', 'regional']: 482 | selector_style['display'] = 'block' 483 | if tab == 'depth-mag': 484 | selector_options.append({'label': '3D Depth Visualization', 'value': '3d'}) 485 | elif tab == 'regional': 486 | selector_options.append({'label': 'High-Risk Zones', 'value': 'risk'}) 487 | valid_values = [opt['value'] for opt in selector_options] 488 | if graph_select not in valid_values: 489 | graph_select = 'none' 490 | 491 | if tab == 'overview': 492 | return (html.Div([ 493 | html.Div([ 494 | dcc.Graph(figure=create_hotspot_map(filtered_df), style={'width': '100%', 'display': 'block'}) 495 | ], style={'width': '100%', 'marginBottom': '30px'}), 496 | html.Div([ 497 | dcc.Graph(figure=create_magnitude_trends(filtered_df), style={'width': '100%', 'display': 'block'}), 498 | html.Div([ 499 | html.H4("Quick Stats", style={'color': '#FFCC70', 'fontSize': '22px', 'textShadow': '1px 1px 3px #000'}), 500 | html.P(f"Total Quakes: {len(filtered_df)}", style={'color': '#FFCC70', 'fontSize': '18px'}), 501 | html.P(f"Max Magnitude: {filtered_df['mag'].max():.1f}" if not filtered_df.empty else "N/A", style={'color': '#FFCC70', 'fontSize': '18px'}) 502 | ], style={'background': 'linear-gradient(135deg, #2a2a2a, #3a3a3a)', 'padding': '20px', 'borderRadius': '15px', 'marginTop': '20px', 'boxShadow': '3px 3px 10px #000'}) 503 | ], style={'width': '100%'}) 504 | ], style={'display': 'flex', 'flexDirection': 'column', 'gap': '40px'}), 505 | selector_style, selector_options, graph_select) 506 | elif tab == 'depth-mag': 507 | content = [ 508 | html.Div([ 509 | dcc.Graph(figure=create_depth_histogram(filtered_df), style={'width': '100%', 'display': 'block'}) 510 | ], style={'width': '100%', 'marginBottom': '30px'}), 511 | html.Div([ 512 | dcc.Graph(figure=create_mag_depth_scatter(filtered_df), style={'width': '100%', 'display': 'block'}) 513 | ], style={'width': '100%', 'marginBottom': '30px'}) 514 | ] 515 | if graph_select == '3d': 516 | content.append(html.Div([ 517 | dcc.Graph(figure=create_3d_depth_plot(filtered_df), style={'width': '100%', 'display': 'block'}) 518 | ], style={'width': '100%', 'marginBottom': '30px'})) 519 | return (html.Div(content, style={'display': 'flex', 'flexDirection': 'column', 'gap': '40px'}), 520 | selector_style, selector_options, graph_select) 521 | elif tab == 'regional': 522 | content = [ 523 | html.Div([ 524 | dcc.Graph(figure=create_region_frequency(filtered_df), style={'width': '100%', 'display': 'block'}) 525 | ], style={'width': '100%', 'marginBottom': '30px'}), 526 | html.Div([ 527 | dcc.Graph(figure=create_region_avg_magnitude(filtered_df), style={'width': '100%', 'display': 'block'}) 528 | ], style={'width': '100%', 'marginBottom': '30px'}) 529 | ] 530 | if graph_select == 'risk': 531 | content.append(html.Div([ 532 | dcc.Graph(figure=create_risk_zones(filtered_df), style={'width': '100%', 'display': 'block'}) 533 | ], style={'width': '100%', 'marginBottom': '30px'})) 534 | return (html.Div(content, style={'display': 'flex', 'flexDirection': 'column', 'gap': '40px'}), 535 | selector_style, selector_options, graph_select) 536 | elif tab == 'mag-breakdown': 537 | return (html.Div([ 538 | html.Div([ 539 | dcc.Graph(figure=create_magnitude_category(filtered_df), style={'width': '100%', 'display': 'block'}) 540 | ], style={'width': '100%', 'marginBottom': '30px'}), 541 | html.Div([ 542 | dcc.Graph(figure=create_magnitude_depth_time(filtered_df), style={'width': '100%', 'display': 'block'}) 543 | ], style={'width': '100%', 'marginBottom': '30px'}) 544 | ], style={'display': 'flex', 'flexDirection': 'column', 'gap': '40px'}), 545 | selector_style, selector_options, graph_select) 546 | 547 | # Callback for reset button 548 | @app.callback( 549 | [Output('date-picker', 'start_date'), 550 | Output('date-picker', 'end_date'), 551 | Output('mag-slider', 'value'), 552 | Output('region-dropdown', 'value'), 553 | Output('depth-slider', 'value')], 554 | Input('btn-reset', 'n_clicks') 555 | ) 556 | def reset_filters(n_clicks): 557 | if n_clicks: 558 | return ( 559 | df['time'].min().date(), 560 | df['time'].max().date(), 561 | [df['mag'].min(), df['mag'].max()], 562 | ['All'], 563 | [df['depth'].min(), df['depth'].max()] 564 | ) 565 | raise PreventUpdate 566 | 567 | # Callback for download button 568 | @app.callback( 569 | Output("download-data", "data"), 570 | Input("btn-download", 'n_clicks'), 571 | [Input('date-picker', 'start_date'), 572 | Input('date-picker', 'end_date'), 573 | Input('mag-slider', 'value'), 574 | Input('region-dropdown', 'value'), 575 | Input('depth-slider', 'value')] 576 | ) 577 | def download_data(n_clicks, start_date, end_date, mag_range, regions, depth_range): 578 | if n_clicks: 579 | filtered_df = df.copy() 580 | start_date = pd.to_datetime(start_date).tz_localize('UTC') 581 | end_date = pd.to_datetime(end_date).tz_localize('UTC') 582 | filtered_df = filtered_df[ 583 | (filtered_df['time'] >= start_date) & 584 | (filtered_df['time'] <= end_date) & 585 | (filtered_df['mag'] >= mag_range[0]) & 586 | (filtered_df['mag'] <= mag_range[1]) & 587 | (filtered_df['depth'] >= depth_range[0]) & 588 | (filtered_df['depth'] <= depth_range[1]) 589 | ] 590 | if regions and regions != ['All']: 591 | if isinstance(regions, str): 592 | regions = [regions] 593 | filtered_df = filtered_df[filtered_df['region'].isin(regions)] 594 | return dcc.send_data_frame(filtered_df.to_csv, "filtered_earthquake_data.csv") 595 | raise PreventUpdate 596 | 597 | # Run the app 598 | if __name__ == '__main__': 599 | app.run(debug=True, port=8050) 600 | --------------------------------------------------------------------------------