Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -354,29 +354,95 @@ class TyphoonAnalyzer:
|
|
354 |
}
|
355 |
|
356 |
def create_tracks_plot(self, data):
|
|
|
357 |
fig = go.Figure()
|
358 |
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
377 |
|
|
|
378 |
fig.update_layout(
|
379 |
-
title=
|
380 |
showlegend=True,
|
381 |
geo=dict(
|
382 |
projection_type='mercator',
|
@@ -385,43 +451,71 @@ class TyphoonAnalyzer:
|
|
385 |
landcolor='rgb(243, 243, 243)',
|
386 |
countrycolor='rgb(204, 204, 204)',
|
387 |
coastlinecolor='rgb(214, 214, 214)',
|
|
|
|
|
388 |
lataxis=dict(range=[0, 50]),
|
389 |
lonaxis=dict(range=[100, 180]),
|
|
|
390 |
)
|
391 |
)
|
392 |
-
|
393 |
-
return fig
|
394 |
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
x = data['ONI']
|
411 |
-
y = data['USA_WIND']
|
412 |
-
slope, intercept = np.polyfit(x, y, 1)
|
413 |
-
fig.add_trace(
|
414 |
-
go.Scatter(
|
415 |
-
x=x,
|
416 |
-
y=slope * x + intercept,
|
417 |
-
mode='lines',
|
418 |
-
name=f'Regression (slope={slope:.2f})',
|
419 |
-
line=dict(color='black', dash='dash')
|
420 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
421 |
)
|
422 |
-
|
423 |
-
return fig
|
424 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
425 |
def create_pressure_analysis(self, data):
|
426 |
fig = px.scatter(data,
|
427 |
x='ONI',
|
@@ -513,7 +607,100 @@ class TyphoonAnalyzer:
|
|
513 |
)
|
514 |
|
515 |
return fig
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
516 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
517 |
def generate_statistics(self, data):
|
518 |
stats = {
|
519 |
'total_typhoons': len(data['SID'].unique()),
|
@@ -566,69 +753,123 @@ def create_interface():
|
|
566 |
|
567 |
analyze_btn = gr.Button("Analyze")
|
568 |
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
-
|
573 |
-
|
574 |
-
wind_plot = gr.Plot()
|
575 |
-
with gr.Tab("Pressure Analysis"):
|
576 |
-
pressure_plot = gr.Plot()
|
577 |
-
with gr.Tab("Clusters"):
|
578 |
-
cluster_plot = gr.Plot()
|
579 |
|
580 |
stats_text = gr.Markdown()
|
581 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
582 |
# Search Tab
|
583 |
with gr.Tab("Typhoon Search"):
|
584 |
with gr.Row():
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
589 |
|
|
|
590 |
def analyze_callback(start_y, start_m, end_y, end_m, enso):
|
591 |
results = analyzer.analyze_typhoon(start_y, start_m, end_y, end_m, enso)
|
592 |
return [
|
593 |
results['tracks'],
|
594 |
results['wind'],
|
595 |
results['pressure'],
|
596 |
-
results['clusters'],
|
597 |
results['stats']
|
598 |
]
|
599 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
600 |
analyze_btn.click(
|
601 |
analyze_callback,
|
602 |
inputs=[start_year, start_month, end_year, end_month, enso_dropdown],
|
603 |
-
outputs=[tracks_plot, wind_plot, pressure_plot,
|
604 |
)
|
605 |
|
606 |
-
|
607 |
-
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
616 |
-
|
617 |
-
|
618 |
-
|
619 |
-
|
620 |
-
|
621 |
-
|
622 |
-
|
623 |
-
|
624 |
-
|
625 |
-
|
626 |
-
|
|
|
|
|
|
|
|
|
|
|
627 |
|
628 |
search_btn.click(
|
629 |
-
|
630 |
-
inputs=[
|
631 |
-
outputs=[
|
632 |
)
|
633 |
|
634 |
return demo
|
|
|
354 |
}
|
355 |
|
356 |
def create_tracks_plot(self, data):
|
357 |
+
"""Create typhoon tracks visualization"""
|
358 |
fig = go.Figure()
|
359 |
|
360 |
+
fig.update_layout(
|
361 |
+
title={
|
362 |
+
'text': 'Typhoon Tracks',
|
363 |
+
'y':0.95,
|
364 |
+
'x':0.5,
|
365 |
+
'xanchor': 'center',
|
366 |
+
'yanchor': 'top'
|
367 |
+
},
|
368 |
+
showlegend=True,
|
369 |
+
legend=dict(
|
370 |
+
yanchor="top",
|
371 |
+
y=0.99,
|
372 |
+
xanchor="left",
|
373 |
+
x=0.01,
|
374 |
+
bgcolor='rgba(255, 255, 255, 0.8)'
|
375 |
+
),
|
376 |
+
geo=dict(
|
377 |
+
projection_type='mercator',
|
378 |
+
showland=True,
|
379 |
+
showcoastlines=True,
|
380 |
+
landcolor='rgb(243, 243, 243)',
|
381 |
+
countrycolor='rgb(204, 204, 204)',
|
382 |
+
coastlinecolor='rgb(214, 214, 214)',
|
383 |
+
showocean=True,
|
384 |
+
oceancolor='rgb(230, 250, 255)',
|
385 |
+
showlakes=True,
|
386 |
+
lakecolor='rgb(230, 250, 255)',
|
387 |
+
lataxis=dict(range=[0, 50]),
|
388 |
+
lonaxis=dict(range=[100, 180]),
|
389 |
+
center=dict(lat=20, lon=140),
|
390 |
+
bgcolor='rgba(255, 255, 255, 0.5)'
|
391 |
+
),
|
392 |
+
paper_bgcolor='rgba(255, 255, 255, 0.5)',
|
393 |
+
plot_bgcolor='rgba(255, 255, 255, 0.5)'
|
394 |
+
)
|
395 |
+
|
396 |
+
for category in COLOR_MAP.keys():
|
397 |
+
category_data = data[data['Category'] == category]
|
398 |
+
|
399 |
+
# Group by SID to get individual typhoon tracks
|
400 |
+
for _, storm in category_data.groupby('SID'):
|
401 |
+
# Create single track for each typhoon
|
402 |
+
track_data = self.typhoon_data[self.typhoon_data['SID'] == storm['SID'].iloc[0]]
|
403 |
+
track_data = track_data.sort_values('ISO_TIME')
|
404 |
+
|
405 |
+
fig.add_trace(go.Scattergeo(
|
406 |
+
lon=track_data['LON'],
|
407 |
+
lat=track_data['LAT'],
|
408 |
+
mode='lines',
|
409 |
+
line=dict(
|
410 |
+
width=2,
|
411 |
+
color=COLOR_MAP[category]
|
412 |
+
),
|
413 |
+
name=category,
|
414 |
+
legendgroup=category,
|
415 |
+
showlegend=True if storm.iloc[0]['SID'] == category_data.iloc[0]['SID'] else False,
|
416 |
+
hovertemplate=(
|
417 |
+
f"Name: {storm['NAME'].iloc[0]}<br>" +
|
418 |
+
f"Category: {category}<br>" +
|
419 |
+
f"Wind Speed: {storm['USA_WIND'].iloc[0]:.1f} kt<br>" +
|
420 |
+
f"Pressure: {storm['WMO_PRES'].iloc[0]:.1f} hPa<br>" +
|
421 |
+
f"Date: {track_data['ISO_TIME'].dt.strftime('%Y-%m-%d %H:%M').iloc[0]}<br>" +
|
422 |
+
f"Lat: %{lat:.2f}°N<br>" +
|
423 |
+
f"Lon: %{lon:.2f}°E<br>" +
|
424 |
+
"<extra></extra>"
|
425 |
+
)
|
426 |
+
))
|
427 |
+
|
428 |
+
return fig
|
429 |
+
def get_typhoons_for_year(self, year):
|
430 |
+
"""Get list of typhoons for a specific year"""
|
431 |
+
year_data = self.typhoon_data[self.typhoon_data['ISO_TIME'].dt.year == year]
|
432 |
+
typhoons = year_data.groupby('SID').first()
|
433 |
+
return [{'label': f"{row['NAME']} ({row.name})", 'value': row.name}
|
434 |
+
for _, row in typhoons.iterrows()]
|
435 |
+
|
436 |
+
def create_typhoon_animation(self, year, typhoon_id):
|
437 |
+
"""Create animated visualization of typhoon path"""
|
438 |
+
storm_data = self.typhoon_data[self.typhoon_data['SID'] == typhoon_id]
|
439 |
+
storm_data = storm_data.sort_values('ISO_TIME')
|
440 |
+
|
441 |
+
fig = go.Figure()
|
442 |
|
443 |
+
# Base map settings
|
444 |
fig.update_layout(
|
445 |
+
title=f"Typhoon Path Animation - {storm_data['NAME'].iloc[0]} ({year})",
|
446 |
showlegend=True,
|
447 |
geo=dict(
|
448 |
projection_type='mercator',
|
|
|
451 |
landcolor='rgb(243, 243, 243)',
|
452 |
countrycolor='rgb(204, 204, 204)',
|
453 |
coastlinecolor='rgb(214, 214, 214)',
|
454 |
+
showocean=True,
|
455 |
+
oceancolor='rgb(230, 250, 255)',
|
456 |
lataxis=dict(range=[0, 50]),
|
457 |
lonaxis=dict(range=[100, 180]),
|
458 |
+
center=dict(lat=20, lon=140)
|
459 |
)
|
460 |
)
|
|
|
|
|
461 |
|
462 |
+
# Create animation frames
|
463 |
+
frames = []
|
464 |
+
for i in range(len(storm_data)):
|
465 |
+
frame = go.Frame(
|
466 |
+
data=[
|
467 |
+
go.Scattergeo(
|
468 |
+
lon=storm_data['LON'].iloc[:i+1],
|
469 |
+
lat=storm_data['LAT'].iloc[:i+1],
|
470 |
+
mode='lines+markers',
|
471 |
+
line=dict(width=2, color='red'),
|
472 |
+
marker=dict(size=8, color='red'),
|
473 |
+
name='Path'
|
474 |
+
)
|
475 |
+
],
|
476 |
+
name=f'frame{i}'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
477 |
)
|
478 |
+
frames.append(frame)
|
479 |
+
|
480 |
+
fig.frames = frames
|
481 |
+
|
482 |
+
# Add animation controls
|
483 |
+
fig.update_layout(
|
484 |
+
updatemenus=[{
|
485 |
+
'buttons': [
|
486 |
+
{
|
487 |
+
'args': [None, {'frame': {'duration': 100, 'redraw': True},
|
488 |
+
'fromcurrent': True}],
|
489 |
+
'label': 'Play',
|
490 |
+
'method': 'animate'
|
491 |
+
},
|
492 |
+
{
|
493 |
+
'args': [[None], {'frame': {'duration': 0, 'redraw': True},
|
494 |
+
'mode': 'immediate',
|
495 |
+
'transition': {'duration': 0}}],
|
496 |
+
'label': 'Pause',
|
497 |
+
'method': 'animate'
|
498 |
+
}
|
499 |
+
],
|
500 |
+
'type': 'buttons',
|
501 |
+
'showactive': False,
|
502 |
+
'x': 0.1,
|
503 |
+
'y': 0,
|
504 |
+
'xanchor': 'right',
|
505 |
+
'yanchor': 'top'
|
506 |
+
}]
|
507 |
)
|
|
|
|
|
508 |
|
509 |
+
info_text = f"""
|
510 |
+
### Typhoon Information
|
511 |
+
- Name: {storm_data['NAME'].iloc[0]}
|
512 |
+
- Start Date: {storm_data['ISO_TIME'].iloc[0]:%Y-%m-%d %H:%M}
|
513 |
+
- End Date: {storm_data['ISO_TIME'].iloc[-1]:%Y-%m-%d %H:%M}
|
514 |
+
- Maximum Wind Speed: {storm_data['USA_WIND'].max():.1f} kt
|
515 |
+
- Minimum Pressure: {storm_data['WMO_PRES'].min():.1f} hPa
|
516 |
+
"""
|
517 |
+
|
518 |
+
return fig, info_text
|
519 |
def create_pressure_analysis(self, data):
|
520 |
fig = px.scatter(data,
|
521 |
x='ONI',
|
|
|
607 |
)
|
608 |
|
609 |
return fig
|
610 |
+
def get_typhoons_for_year(self, year):
|
611 |
+
"""Get list of typhoons for a specific year"""
|
612 |
+
year_data = self.typhoon_data[self.typhoon_data['SEASON'] == year]
|
613 |
+
unique_typhoons = year_data.groupby('SID').first().reset_index()
|
614 |
+
return [
|
615 |
+
{'label': f"{row['NAME']} ({row['ISO_TIME'].strftime('%Y-%m-%d')})",
|
616 |
+
'value': row['SID']}
|
617 |
+
for _, row in unique_typhoons.iterrows()
|
618 |
+
]
|
619 |
|
620 |
+
def search_typhoon_details(self, year, typhoon_id):
|
621 |
+
"""Get detailed information for a specific typhoon"""
|
622 |
+
if not typhoon_id:
|
623 |
+
return None, "Please select a typhoon"
|
624 |
+
|
625 |
+
storm_data = self.typhoon_data[self.typhoon_data['SID'] == typhoon_id]
|
626 |
+
storm_data = storm_data.sort_values('ISO_TIME')
|
627 |
+
|
628 |
+
# Create track plot
|
629 |
+
fig = self.create_single_typhoon_plot(storm_data)
|
630 |
+
|
631 |
+
# Create detailed information text
|
632 |
+
info = self.create_typhoon_info_text(storm_data)
|
633 |
+
|
634 |
+
return fig, info
|
635 |
+
|
636 |
+
def create_single_typhoon_plot(self, storm_data):
|
637 |
+
"""Create a detailed plot for a single typhoon"""
|
638 |
+
fig = go.Figure()
|
639 |
+
|
640 |
+
fig.update_layout(
|
641 |
+
title=f"Typhoon Track - {storm_data['NAME'].iloc[0]} ({storm_data['SEASON'].iloc[0]})",
|
642 |
+
showlegend=True,
|
643 |
+
geo=dict(
|
644 |
+
projection_type='mercator',
|
645 |
+
showland=True,
|
646 |
+
showcoastlines=True,
|
647 |
+
landcolor='rgb(243, 243, 243)',
|
648 |
+
countrycolor='rgb(204, 204, 204)',
|
649 |
+
coastlinecolor='rgb(214, 214, 214)',
|
650 |
+
showocean=True,
|
651 |
+
oceancolor='rgb(230, 250, 255)',
|
652 |
+
lataxis=dict(range=[0, 50]),
|
653 |
+
lonaxis=dict(range=[100, 180]),
|
654 |
+
center=dict(lat=20, lon=140)
|
655 |
+
)
|
656 |
+
)
|
657 |
+
|
658 |
+
# Add main track
|
659 |
+
fig.add_trace(go.Scattergeo(
|
660 |
+
lon=storm_data['LON'],
|
661 |
+
lat=storm_data['LAT'],
|
662 |
+
mode='lines+markers',
|
663 |
+
line=dict(width=2, color='red'),
|
664 |
+
marker=dict(
|
665 |
+
size=8,
|
666 |
+
color=storm_data['USA_WIND'],
|
667 |
+
colorscale='Viridis',
|
668 |
+
showscale=True,
|
669 |
+
colorbar=dict(title='Wind Speed (kt)')
|
670 |
+
),
|
671 |
+
text=[f"Time: {time:%Y-%m-%d %H:%M}<br>Wind: {wind:.1f} kt<br>Pressure: {pres:.1f} hPa"
|
672 |
+
for time, wind, pres in zip(storm_data['ISO_TIME'],
|
673 |
+
storm_data['USA_WIND'],
|
674 |
+
storm_data['WMO_PRES'])],
|
675 |
+
hoverinfo='text'
|
676 |
+
))
|
677 |
+
|
678 |
+
return fig
|
679 |
+
|
680 |
+
def create_typhoon_info_text(self, storm_data):
|
681 |
+
"""Create detailed information text for a typhoon"""
|
682 |
+
max_wind = storm_data['USA_WIND'].max()
|
683 |
+
min_pressure = storm_data['WMO_PRES'].min()
|
684 |
+
duration = (storm_data['ISO_TIME'].max() - storm_data['ISO_TIME'].min()).total_seconds() / 3600 # hours
|
685 |
+
|
686 |
+
return f"""
|
687 |
+
### Typhoon Details: {storm_data['NAME'].iloc[0]}
|
688 |
+
|
689 |
+
**Timing Information:**
|
690 |
+
- Start: {storm_data['ISO_TIME'].min():%Y-%m-%d %H:%M}
|
691 |
+
- End: {storm_data['ISO_TIME'].max():%Y-%m-%d %H:%M}
|
692 |
+
- Duration: {duration:.1f} hours
|
693 |
+
|
694 |
+
**Intensity Metrics:**
|
695 |
+
- Maximum Wind Speed: {max_wind:.1f} kt
|
696 |
+
- Minimum Pressure: {min_pressure:.1f} hPa
|
697 |
+
- Maximum Category: {self.categorize_typhoon(max_wind)}
|
698 |
+
|
699 |
+
**Track Information:**
|
700 |
+
- Starting Position: {storm_data['LAT'].iloc[0]:.1f}°N, {storm_data['LON'].iloc[0]:.1f}°E
|
701 |
+
- Ending Position: {storm_data['LAT'].iloc[-1]:.1f}°N, {storm_data['LON'].iloc[-1]:.1f}°E
|
702 |
+
- Total Track Points: {len(storm_data)}
|
703 |
+
"""
|
704 |
def generate_statistics(self, data):
|
705 |
stats = {
|
706 |
'total_typhoons': len(data['SID'].unique()),
|
|
|
753 |
|
754 |
analyze_btn = gr.Button("Analyze")
|
755 |
|
756 |
+
with gr.Row():
|
757 |
+
tracks_plot = gr.Plot()
|
758 |
+
with gr.Row():
|
759 |
+
wind_plot = gr.Plot()
|
760 |
+
pressure_plot = gr.Plot()
|
|
|
|
|
|
|
|
|
|
|
761 |
|
762 |
stats_text = gr.Markdown()
|
763 |
|
764 |
+
# Clustering Analysis Tab
|
765 |
+
with gr.Tab("Clustering Analysis"):
|
766 |
+
with gr.Row():
|
767 |
+
cluster_year = gr.Slider(1900, 2024, 2000, label="Year")
|
768 |
+
n_clusters = gr.Slider(2, 20, 5, label="Number of Clusters")
|
769 |
+
|
770 |
+
cluster_btn = gr.Button("Analyze Clusters")
|
771 |
+
cluster_plot = gr.Plot()
|
772 |
+
cluster_stats = gr.Markdown()
|
773 |
+
|
774 |
+
# Animation Tab
|
775 |
+
with gr.Tab("Typhoon Animation"):
|
776 |
+
with gr.Row():
|
777 |
+
animation_year = gr.Slider(
|
778 |
+
minimum=1900,
|
779 |
+
maximum=2024,
|
780 |
+
value=2024,
|
781 |
+
step=1,
|
782 |
+
label="Select Year"
|
783 |
+
)
|
784 |
+
|
785 |
+
with gr.Row():
|
786 |
+
animation_typhoon = gr.Dropdown(
|
787 |
+
choices=[],
|
788 |
+
label="Select Typhoon",
|
789 |
+
interactive=True
|
790 |
+
)
|
791 |
+
|
792 |
+
animation_btn = gr.Button("Animate Typhoon Path", variant="primary")
|
793 |
+
animation_plot = gr.Plot()
|
794 |
+
animation_info = gr.Markdown()
|
795 |
+
|
796 |
# Search Tab
|
797 |
with gr.Tab("Typhoon Search"):
|
798 |
with gr.Row():
|
799 |
+
search_year = gr.Slider(
|
800 |
+
minimum=1900,
|
801 |
+
maximum=2024,
|
802 |
+
value=2024,
|
803 |
+
step=1,
|
804 |
+
label="Select Year"
|
805 |
+
)
|
806 |
+
|
807 |
+
with gr.Row():
|
808 |
+
search_typhoon = gr.Dropdown(
|
809 |
+
choices=[],
|
810 |
+
label="Select Typhoon",
|
811 |
+
interactive=True
|
812 |
+
)
|
813 |
+
|
814 |
+
search_btn = gr.Button("Show Typhoon Details", variant="primary")
|
815 |
+
search_plot = gr.Plot()
|
816 |
+
search_info = gr.Markdown()
|
817 |
|
818 |
+
# Event handlers
|
819 |
def analyze_callback(start_y, start_m, end_y, end_m, enso):
|
820 |
results = analyzer.analyze_typhoon(start_y, start_m, end_y, end_m, enso)
|
821 |
return [
|
822 |
results['tracks'],
|
823 |
results['wind'],
|
824 |
results['pressure'],
|
|
|
825 |
results['stats']
|
826 |
]
|
827 |
|
828 |
+
def cluster_callback(year, n_clusters):
|
829 |
+
return analyzer.analyze_clusters(year, n_clusters)
|
830 |
+
|
831 |
+
def update_typhoon_choices(year):
|
832 |
+
typhoons = analyzer.get_typhoons_for_year(year)
|
833 |
+
return gr.Dropdown.update(choices=typhoons, value=None)
|
834 |
+
|
835 |
+
# Connect events for main analysis
|
836 |
analyze_btn.click(
|
837 |
analyze_callback,
|
838 |
inputs=[start_year, start_month, end_year, end_month, enso_dropdown],
|
839 |
+
outputs=[tracks_plot, wind_plot, pressure_plot, stats_text]
|
840 |
)
|
841 |
|
842 |
+
# Connect events for clustering
|
843 |
+
cluster_btn.click(
|
844 |
+
cluster_callback,
|
845 |
+
inputs=[cluster_year, n_clusters],
|
846 |
+
outputs=[cluster_plot, cluster_stats]
|
847 |
+
)
|
848 |
+
|
849 |
+
# Connect events for Animation tab
|
850 |
+
animation_year.change(
|
851 |
+
update_typhoon_choices,
|
852 |
+
inputs=[animation_year],
|
853 |
+
outputs=[animation_typhoon]
|
854 |
+
)
|
855 |
+
|
856 |
+
animation_btn.click(
|
857 |
+
analyzer.create_typhoon_animation,
|
858 |
+
inputs=[animation_year, animation_typhoon],
|
859 |
+
outputs=[animation_plot, animation_info]
|
860 |
+
)
|
861 |
+
|
862 |
+
# Connect events for Search tab
|
863 |
+
search_year.change(
|
864 |
+
update_typhoon_choices,
|
865 |
+
inputs=[search_year],
|
866 |
+
outputs=[search_typhoon]
|
867 |
+
)
|
868 |
|
869 |
search_btn.click(
|
870 |
+
analyzer.search_typhoon_details,
|
871 |
+
inputs=[search_year, search_typhoon],
|
872 |
+
outputs=[search_plot, search_info]
|
873 |
)
|
874 |
|
875 |
return demo
|