From e6e7dc787398f226564ef1b732a82dfd4affef69 Mon Sep 17 00:00:00 2001
From: "alexis.dereeper_ird.fr" <alexis.dereeper@ird.fr>
Date: Tue, 11 Mar 2025 15:36:00 +0000
Subject: [PATCH] add legend into geographical map

---
 dash/pages/sample-tracker.py | 273 +++++++++++++++++++----------------
 1 file changed, 152 insertions(+), 121 deletions(-)

diff --git a/dash/pages/sample-tracker.py b/dash/pages/sample-tracker.py
index 79f2623..b83535c 100644
--- a/dash/pages/sample-tracker.py
+++ b/dash/pages/sample-tracker.py
@@ -1,9 +1,11 @@
 from dash import Dash, html, dcc, Input, Output, State, callback, dash_table
-#from dash import Dash, dcc, html, dash_table, Input, Output, State, callback
+
+import branca
   
 from grist_api import GristDocAPI
 import os
 import pandas as pd
+import random
 import plotly.express as px
 
 import plotly.graph_objects as go
@@ -35,6 +37,9 @@ import folium
 import folium.plugins
 import pandas.io.sql as psql
 
+import matplotlib.pyplot as plt
+from matplotlib import colors as mcolors
+
 
 import dash_bio as dashbio
 
@@ -44,6 +49,7 @@ dash.register_page(__name__,path='/sample-tracker')
 
 server_url = "https://bioinfophim-grist.ird.fr"
 working_dir = "/mnt/c/Users/dereeper/Documents/formation_python_scientifique_2022/dash"
+
 with open("grist_config.yml", "r") as yaml_file:
     conf = yaml.safe_load(yaml_file)
     server_url = conf["server_url"]
@@ -91,28 +97,6 @@ df=pd.merge(df_sequenced_strains,df1, left_on='source_strain_id', right_on='id_y
 
 df_diag = df[df['Species']=='Unknown']
 
-year_colors = {
-    "2014": "lightgray",
-    "2015": "darkgreen",
-    "2016": "darkpurple",
-    "2017": "beige",
-    "2018": "lightblue",
-    "2019": "pink",
-    "2020": "green",
-    "2021": "red",
-    "2022": "black",
-    "2023": "blue"
-}
-
-species_colors = {
-    "Xanthomonas oryzae oryzae": "lightblue",
-    "Xanthomonas oryzae oryzicola": "blue",
-    "Xanthomonas hortorum": "orange",
-    "Xanthomonas phaseoli": "pink",
-    "Xanthomonas translucens": "green",
-    "Xanthomonas axonopodis": "red",
-    "Xanthomonas vasicola": "black"
-}
 
 
 ############################################
@@ -156,7 +140,7 @@ data_summary_filtered_md_template = 'Selected strains'
 data_summary_filtered_md = data_summary_filtered_md_template.format(len(df))
 
 
-
+session = random.randint(1, 9000000)
 
 #print(df.head())
 
@@ -283,23 +267,33 @@ layout = html.Div(className='app-body', children=[
         # The Visuals
     dcc.Tabs(id='tab', children=[
         dcc.Tab(label='Geographical map', children=[
-            html.Div(className="four columns pretty_container", children=[
-                html.Div(className="row", children=[
-                    html.Label('Marker Clustering'),
-                    daq.BooleanSwitch(id="clustering", on=True, style={'width': '10vh','margin-left': '20px'},)
-                ]),
-
-            ]),
-            html.Div(className="four columns pretty_container", children=[
-                html.Label('Colorizing map by'),
+            html.Br(),
+            html.Div(className="row", children=[
+                html.Label('Marker Clustering'),
+                daq.BooleanSwitch(id="clustering", on=True, style={'width': '10vh','margin-left': '5px'},),
+                
+                html.Label('Map tiles: '),
+                dcc.Dropdown(id='tiles',
+                    style={'width': '20vh','margin-left': '5px','margin-right': '10px'},
+                    placeholder='OpenStreetMap',
+                    options=['OpenStreetMap','Satellite'],
+                    value='OpenStreetMap',
+                    multi=False),
+                html.Label('Colorizing map by: '),
                 dcc.Dropdown(id='colorizingmap',
-                            placeholder='Species',
-                            options=list_colorizing_map,
-                            value='Species',
-                            multi=False),
+                    style={'width': '20vh','margin-left': '5px','margin-right': '10px'},
+                    placeholder='Species',
+                    options=list_colorizing_map,
+                    value='Species',
+                    multi=False),
+                
             ]),
+            html.Br(),
+
             dcc.Loading(
-                html.Iframe(id='map',src="https://webphim.ird.fr/CIX/testmap.html",style={"height": "600px", "width": "100%"})
+                #html.Iframe(id='map',src="https://webphim.ird.fr/CIX/testmap.html",style={"height": "600px", "width": "100%"})
+                html.Iframe(id='map',style={"height": "900px", "width": "100%"})
+
             ),
              #dcc.Graph(id="map",figure=fig_scattergeo),
             ]),
@@ -320,14 +314,17 @@ layout = html.Div(className='app-body', children=[
                     ),
             ]),
         dcc.Tab(label='Statistics', children=[
-            
-            html.Label('Colorizing histograms by'),
-            dcc.Dropdown(id='colorizing',
-                            placeholder='Species',
-                            options=list_colorizing,
-                            value='Species',
-                            multi=False),
-            
+            html.Br(),
+            html.Div(className="row", children=[
+                html.Label('Colorizing histograms by'),
+                dcc.Dropdown(id='colorizing',
+                                placeholder='Species',
+                                style={'width': '40vh','margin-left': '5px'},
+                                options=list_colorizing,
+                                value='Species',
+                                multi=False),
+                
+            ]),
 
             dcc.Loading(
                 
@@ -384,15 +381,16 @@ layout = html.Div(className='app-body', children=[
     Input('year', 'value'),
     Input('pathovar', 'value'),
     Input('colorizing', 'value'),
-    Input('colorizingmap', 'value'),
-    Input('clustering', 'on'),
+    State('colorizingmap', 'value'),
+    State('tiles', 'value'),
+    State('clustering', 'on'),
     Input('gps_infered', 'value'),
     Input('include_no_date', 'on'),
     Input('sequenced', 'on'),
     #Input('datatable-paging', "page_current"),
     #Input('datatable-paging', "page_size"),
-     )
-def update_graph(sp_name, cnt_name, wt_name, year_range, pathovar_name, colorizing_name,colorizingmap_name,clustering_name,gps_infered,include_no_date,sequenced):
+    )
+def update_graph(sp_name, cnt_name, wt_name, year_range, pathovar_name, colorizing_name,colorizingmap_name,tiles,clustering_name,gps_infered,include_no_date,sequenced):
 
     
 
@@ -478,10 +476,54 @@ def update_graph(sp_name, cnt_name, wt_name, year_range, pathovar_name, colorizi
     ###########################################################################
     # Geographical map of CIX
     ###########################################################################
-    map_with_clusters = folium.Map(location=[11,1], zoom_start=3)
-    marker_cluster = map_with_clusters
-    if clustering_name:
-        marker_cluster = folium.plugins.MarkerCluster().add_to(map_with_clusters)
+    dff3.to_csv(working_dir + "/" + str(session) + ".matrix.tsv",sep="\t")
+    updateMap(clustering_name,colorizingmap_name,tiles)
+
+    
+
+
+
+    ###########################################################################
+    # Geographical map of diagnostics
+    ###########################################################################
+    map_diag = folium.Map(location=[11,1], zoom_start=3)
+    # for idx  in df_diag.index:
+    #     lat = df_diag.loc[idx]['GPS_Latitude']
+    #     lon = df_diag.loc[idx]['GPS_Longitude']
+    #     diagnostics = df_diag.loc[idx]['diagnostics']
+    #     host = df_diag.loc[idx]['host']
+    #     my_color = "blue"
+    #     if (diagnostics=="Négatif"):
+    #         my_color="red"
+    #     elif (diagnostics=="Xoo"):
+    #         my_color="green"
+    #     elif (diagnostics=="Xoo et Xoc"):
+    #         my_color="blue"
+
+    fn2= working_dir+'/'+str(session)+'.map_diagnostics.html'
+    map_diag.save(fn2)
+
+    html = generate_html(dff3)
+    open(working_dir+"/"+str(session)+".table.html", "w").write(html)
+
+    table = dff3.to_dict('records')
+
+    return open(working_dir+"/"+str(session)+'.testmap.html', 'r').read(),fig,fig2,fig4,open(working_dir+"/"+str(session)+'.map_diagnostics.html', 'r').read(),str(len(dff3)),'Current selection : {} strains'.format(len(dff3)),table
+
+
+
+@callback(
+    Output('map','srcDoc', allow_duplicate=True),
+    Input('clustering', 'on'),
+    Input('colorizingmap', 'value'),
+    Input('tiles', 'value'),
+    prevent_initial_call=True
+)
+
+def updateMap(clustering_name,colorizingmap_name,tiles):
+
+    map_with_clusters = folium.Map(location=[11,1], control_scale=True, zoom_start=3)
+    if tiles != 'OpenStreetMap':
         tile = folium.TileLayer(
                 tiles = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
                 attr = 'Esri',
@@ -490,35 +532,60 @@ def update_graph(sp_name, cnt_name, wt_name, year_range, pathovar_name, colorizi
                 control = True
                 ).add_to(map_with_clusters)
 
+
+    dff3 = pd.read_csv(working_dir + "/" + str(session) + ".matrix.tsv",sep="\t")
+    dff3['Sampling_date'] = pd.to_datetime(dff3.Sampling_date, format='%Y-%m-%d')
+    
+    marker_cluster = map_with_clusters
+    if clustering_name:
+        marker_cluster = folium.plugins.MarkerCluster().add_to(map_with_clusters)
     else:
         marker_cluster = map_with_clusters
 
-    print("d")
-
     dff3.GPS_Latitude
-
-    #dff3['sampling_date'] = dff3['sampling_date'].replace(['1900'], 'NA')
     
-    # remove entries with empty or NA in GPS coordinates
-    df_geo = dff3[abs(dff3.GPS_Latitude) > 0]
-
-
-    # fig_scattergeo = go.Figure(data=go.Scattergeo(
-    #     lon = df_geo['GPS_Longitude'],
-    #     lat = df_geo['GPS_Latitude'],
-    #     text = df['strain_id'],
-    #     mode = 'markers',
-    #     #marker_color = df_geo['Sampling_date'].year,
-    #     ))
 
-    # fig_scattergeo = px.scatter_geo(df_geo, 
-    #                  lat = 'GPS_Latitude', 
-    #                  lon = 'GPS_Longitude', 
-    #                  geojson='geometry', 
-    #                  scope='africa',
-    #                  center=dict(lat=51.0057, lon=13.7274))
+    #predefined_colors
 
+    # remove entries with empty or NA in GPS coordinates
+    df_geo = dff3[abs(dff3.GPS_Latitude) > 0]
 
+    
+    
+    species_colors = {}
+    list_of_species = df_geo['Species'].unique().tolist()
+    list_of_years = df_geo['Sampling_date'].unique().tolist()
+    dict_year = {}
+    for y in list_of_years:
+        dict_year[y.year]=1
+    
+    sorted_years = sorted(list(dict_year.keys()))
+    predefined_colors = list(mcolors.CSS4_COLORS.keys()) 
+
+    legend_html = '''
+    {% macro html(this, kwargs) %}
+    <div style="position: fixed; 
+        bottom: 50px; left: 0px; width: 250px; height: 700px; 
+        border:2px solid grey; z-index:9999; font-size:14px;
+        background-color:white; opacity: 0.85;">
+        &nbsp; <b>Legend</b> <br>
+        '''
+    i = 0
+    if colorizingmap_name == "Year of sampling":
+        for y in sorted_years:
+            color = predefined_colors[i]
+            species_colors[int(y)]=color
+            legend_html = legend_html + "&nbsp; <i class='fa fa-circle' style='color:"+str(color)+"'></i> &nbsp; " + str(y) + " &nbsp;<br>"
+            i+=1
+    else:
+        for sp in list_of_species:
+                
+            color = predefined_colors[i]
+            species_colors[sp]=color
+            legend_html = legend_html + "&nbsp; <i class='fa fa-circle' style='color:"+str(color)+"'></i> &nbsp; " + str(sp) + " &nbsp;<br>"
+            i+=1
+    legend_html = legend_html + "</div>"
+    legend_html = legend_html + "{% endmacro %}"
 
     for idx  in df_geo.index:
     
@@ -534,19 +601,13 @@ def update_graph(sp_name, cnt_name, wt_name, year_range, pathovar_name, colorizi
         CIX = str(df_geo.loc[idx]['strain_id'])
         wt = df_geo.loc[idx]['WT_DER_RES']
 
-        #species_pathovar = species + " " + pathovar
-
         my_color="blue"
-        if (colorizingmap_name == "Year of sampling" and year in year_colors.keys()):
-            my_color = year_colors[year]
+        if (colorizingmap_name == "Year of sampling" and int(year) in list(species_colors.keys())):
+            my_color = species_colors[int(year)]
 
         if (colorizingmap_name == "Species" and species in species_colors.keys()):
             my_color = species_colors[species]
 
-
-            
-
-
         folium.CircleMarker(
                 location=(lat,lon),
                 radius=8,
@@ -560,49 +621,19 @@ def update_graph(sp_name, cnt_name, wt_name, year_range, pathovar_name, colorizi
                 ).add_to(marker_cluster)
 
 
-    fn= working_dir + '/testmap.html'
-    marker_cluster.save(fn)
-
-    print("e")
-
+    legend = branca.element.MacroElement()
+    legend._template = branca.element.Template(legend_html)
 
-    ###########################################################################
-    # Geographical map of diagnostics
-    ###########################################################################
-    map_diag = folium.Map(location=[11,1], zoom_start=3)
-    # for idx  in df_diag.index:
-    #     lat = df_diag.loc[idx]['GPS_Latitude']
-    #     lon = df_diag.loc[idx]['GPS_Longitude']
-    #     diagnostics = df_diag.loc[idx]['diagnostics']
-    #     host = df_diag.loc[idx]['host']
-    #     my_color = "blue"
-    #     if (diagnostics=="Négatif"):
-    #         my_color="red"
-    #     elif (diagnostics=="Xoo"):
-    #         my_color="green"
-    #     elif (diagnostics=="Xoo et Xoc"):
-    #         my_color="blue"
-
-    fn2= working_dir+'/map_diagnostics.html'
-    map_diag.save(fn2)
-
-    html = generate_html(dff3)
-    open(working_dir+"/table.html", "w").write(html)
-
-    table = dff3.to_dict('records')
+    # Add the legend to the map
+    marker_cluster.get_root().add_child(legend)
 
-    
-
-    
-    
-    return open(working_dir+'/testmap.html', 'r').read(),fig,fig2,fig4,open(working_dir+'/map_diagnostics.html', 'r').read(),str(len(dff3)),'Current selection : {} strains'.format(len(dff3)),table
-
-    #return fig_scattergeo,fig,fig2,fig4,open(working_dir+'/map_diagnostics.html', 'r').read(),str(len(dff3)),'Current selection : {} strains'.format(len(dff3)),table
-    #,open('table.html', 'r').read()
-    #sortable.html.table(dff3, 'sample.html')
 
+    fn= working_dir + "/"+str(session)+ '.testmap.html'
+    marker_cluster.save(fn)
 
+    data_returned = open(working_dir+"/"+str(session)+'.testmap.html', 'r').read()
 
+    return data_returned
 
 def generate_html(dataframe: pd.DataFrame):
     # get the table HTML from the dataframe
-- 
GitLab