Py.Cafe

Sindhup24/

solara-shapefile-overlay

Interactive Data Visualization for ID and State Analysis

DocsPricing
  • EA.cst
  • EA.dbf
  • EA.prj
  • EA.shp
  • EA.shx
  • app.py
  • requirements.txt
  • updated_fc_20240429.csv
app.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
import pandas as pd
import geopandas as gpd
import folium
from folium.plugins import TimestampedGeoJson
import solara
import matplotlib.pyplot as plt
import matplotlib.dates as mdates

# Load the CSV file
file_path = 'updated_fc_20240429.csv'  # Replace with your actual CSV file path
data = pd.read_csv(file_path)

# Load the shapefile (e.g., East African countries shapefile)
shapefile_path = 'EA.shp'  # Replace with your actual shapefile path
gdf = gpd.read_file(shapefile_path)

# Extract unique states
unique_states = data['state'].unique().tolist()
selected_state = solara.reactive(unique_states[0])
selected_id = solara.reactive("")
selected_name = solara.reactive("")
generate_trigger = solara.reactive(0)
show_values = solara.reactive(False)

def update_ids_and_names():
    filtered_data = data[data['state'] == selected_state.value]
    unique_ids = filtered_data['Id'].unique().tolist()
    if unique_ids:
        selected_id.value = unique_ids[0]
        update_name()
    else:
        selected_id.value = ""
        selected_name.value = ""

def update_name():
    filtered_data = data[data['Id'] == selected_id.value]
    if not filtered_data.empty:
        selected_name.value = filtered_data['Name'].iloc[0]
    else:
        selected_name.value = ""

# Function to plot line chart using matplotlib
def plot_line_chart(df, id, show_values):
    filtered_data = df[df['Id'] == id]
    if filtered_data.empty:
        print("No data available for the selected ID.")
        return None
    
    # Ensure date is in datetime format
    filtered_data['date'] = pd.to_datetime(filtered_data['date'])
    
    fig, ax1 = plt.subplots(figsize=(10, 6))
    ax2 = ax1.twinx()  # Instantiate a second y-axis that shares the same x-axis
    
    ax1.plot(filtered_data['date'], filtered_data['value'], marker='o', linestyle='-', color='blue', label='Value')
    ax1.set_xlabel('Date', fontweight='bold')
    ax1.set_ylabel('Value', fontweight='bold')
    ax1.set_title(f'Value over Time for ID {id}', fontweight='bold')
    ax1.grid(True)
    
    # Add all dates to x-axis
    ax1.set_xticks(filtered_data['date'])
    ax1.set_xticklabels(filtered_data['date'].dt.strftime('%Y-%m-%d'), rotation=45, ha='right')
    ax1.xaxis.set_major_locator(mdates.DayLocator(interval=1))
    
    # Conditionally add value annotations next to each point
    if show_values:
        for i, txt in enumerate(filtered_data['value']):
            ax1.annotate(f"{txt:.2f}", (filtered_data['date'].iloc[i], filtered_data['value'].iloc[i]), 
                         textcoords="offset points", xytext=(0,5), ha='center')
    
    plt.subplots_adjust(right=0.7, top=0.95, bottom=0.25)  # Adjust the top, right, and bottom of the graph
    
    # Add horizontal lines for ActiveThemeWarningLevelValues with different line styles and colors on secondary y-axis
    warning_levels = {
        'ActiveThemeWarningLevelValues_Normal Flow': ('-', 'green', 'ATWLV_Normal'),
        'ActiveThemeWarningLevelValues_2 years Return Period Flow': ('--', 'red', 'ATWLV_2 years'),
        'ActiveThemeWarningLevelValues_5 years Return Period Flow': ('-.', 'yellow', 'ATWLV_5 years'),
        'ActiveThemeWarningLevelValues_10 years Return Period Flow': (':', 'purple', 'ATWLV_10 years'),
        'ActiveThemeWarningLevelValues_15 years Return Period Flow': ((0, (3, 1, 1, 1)), 'orange', 'ATWLV_15 years'),
        'ActiveThemeWarningLevelValues_20 years Return Period Flow': ((0, (5, 1)), 'cyan', 'ATWLV_20 years')
    }
    
    for level, (linestyle, color, short_name) in warning_levels.items():
        if level in df.columns:
            value = df[level].unique()[0]
            ax2.axhline(y=value, color=color, linestyle=linestyle, label=short_name)
    
    # Adjust legend positioning to the top right
    handles1, labels1 = ax1.get_legend_handles_labels()
    handles2, labels2 = ax2.get_legend_handles_labels()
    ax1.legend(handles1 + handles2, labels1 + labels2, loc='upper right', bbox_to_anchor=(1.35, 1.0), borderaxespad=0.)
    
    return fig

# Function to add legends to the map
def add_legend(m):
    legend_html = '''
     <div style="position: fixed; 
                 bottom: 50px; right: 50px; width: 150px; height: 150px; 
                 border:2px solid grey; z-index:9999; font-size:14px;
                 background-color:white;
                 ">
     &nbsp;<b>Legend</b><br>
     &nbsp;<i class="fa fa-circle" style="color:green"></i>&nbsp; value < 10<br>
     &nbsp;<i class="fa fa-circle" style="color:blue"></i>&nbsp; 10 <= value < 20<br>
     &nbsp;<i class="fa fa-circle" style="color:orange"></i>&nbsp; 20 <= value < 30<br>
     &nbsp;<i class="fa fa-circle" style="color:red"></i>&nbsp; value >= 30
     </div>
     '''
    m.get_root().html.add_child(folium.Element(legend_html))

# Define the bounding box or initial center for East Africa (approximate)
ea_center = [0.5, 37.0]  # Latitude and Longitude for EA region (adjust as needed)
ea_zoom_start = 5  # Adjust zoom level to focus on EA region

# Function to plot map with timeline slider and overlay shapefile
def plot_map_with_slider(df, highlight_id=None):
    # Create a map centered around the EA region with a specific zoom level
    m = folium.Map(location=ea_center, zoom_start=ea_zoom_start)
    
    # Use the correct column name for filtering East African countries
    gdf_ea = gdf[gdf['ADM0_NAME'].isin(["Kenya", "Tanzania", "Uganda", "Rwanda", "Burundi", "Ethiopia", "South Sudan", "Somalia", "Eritrea", "Djibouti"])]
    
    # Overlay filtered shapefile on the map
    folium.GeoJson(gdf_ea, name="East Africa Overlay").add_to(m)
    
    # Define a function to determine color based on value
    def get_color(value):
        if value < 10:
            return 'green'
        elif 10 <= value < 20:
            return 'blue'
        elif 20 <= value < 30:
            return 'orange'
        else:
            return 'red'

    # Define a function to determine radius based on value
    def get_radius(value):
        if value < 10:
            return 8
        else:
            return 10

    features = []
    for i, row in df.iterrows():
        feature = {
            'type': 'Feature',
            'geometry': {
                'type': 'Point',
                'coordinates': [row['XCoordinate'], row['YCoordinate']]
            },
            'properties': {
                'time': row['date'],
                'popup': f"ID: {row['Id']} - {row['Name']}: ({row['XCoordinate']}, {row['YCoordinate']}) Value: {row['value']}",
                'icon': 'circle',
                'iconstyle': {
                'color': get_color(row['value']),
                'fillColor': get_color(row['value']),
                'fillOpacity': 0.6,
                'radius': get_radius(row['value'])
                }
            }
        }
        if highlight_id and row['Id'] == highlight_id:
            # Adding blinking effect for the highlighted ID
            feature['properties']['iconstyle']['className'] = 'blinking'
        features.append(feature)
    
    TimestampedGeoJson({
        'type': 'FeatureCollection',
        'features': features
    }, period='P1D', add_last_point=True, auto_play=False, loop=False).add_to(m)

    # Add legend to the map
    add_legend(m)

    # Add CSS for blinking effect
    blinking_css = '''
    <style>
    .blinking {
        animation: blinker 1s linear infinite;
    }
    @keyframes blinker {
        50% { opacity: 0; }
    }
    </style>
    '''
    m.get_root().html.add_child(folium.Element(blinking_css))

    return m

@solara.component
def Controls():
    # State dropdown
    solara.Select('State', values=unique_states, value=selected_state)

    # Update IDs based on selected state
    filtered_ids = data[data['state'] == selected_state.value]['Id'].unique().tolist()
    if selected_id.value not in filtered_ids:
        selected_id.value = filtered_ids[0] if filtered_ids else ""
    
    # Update Name based on selected ID
    filtered_names = data[data['Id'] == selected_id.value]['Name'].unique().tolist()
    if selected_name.value not in filtered_names:
        selected_name.value = filtered_names[0] if filtered_names else ""

    # ID dropdown
    solara.Select('ID', values=filtered_ids, value=selected_id)

    # Name dropdown
    solara.Select('Name', values=filtered_names, value=selected_name)

    # Buttons to trigger chart generation and toggle value display
    def generate_chart():
        generate_trigger.value += 1

    def toggle_show_values():
        show_values.value = not show_values.value
        generate_trigger.value += 1

    solara.Button(label="Generate Chart", on_click=generate_chart, icon_name="mdi-chart-bar")
    solara.Button(label="Show Point Values", on_click=toggle_show_values, icon_name="mdi-eye")

@solara.component
def View():
    with solara.VBox() as main:
        if generate_trigger.value > 0 and selected_id.value:
            m = plot_map_with_slider(data, highlight_id=selected_id.value)
            fig = plot_line_chart(data, selected_id.value, show_values.value)
            if fig:
                solara.HTML(tag="div", unsafe_innerHTML=m._repr_html_())
                solara.FigureMatplotlib(fig)
                solara.Info("Map and chart have been updated.")
            else:
                solara.Warning("No data available for the selected ID.")
        else:
            m = plot_map_with_slider(data)
            solara.HTML(tag="div", unsafe_innerHTML=m._repr_html_())
            solara.Warning("Please select a state, ID, and Name.")
    return main

@solara.component
def Page():
    with solara.Sidebar():
        Controls()
    View()

# Display the page
Page()