Customize PyGal WorldMap chart's tooltip - python

I'm using pygal for generate a world map chart in Google Colab.
The problem I'm facing is with the tooltip. I want to show only in the tooltip the name of the country and the number of videos found.
The result so far I've managed to get is as follows:
Worldmap chart current output:
You can see there that the tooltip is composed as follows:
Country name
Number of videos
Country name: 1
Example:
United States
73 videos
Unites States: 1
Using the sample shown in the screenshot and in the sample above, what I want to get is
Example:
United States
73 videos
OR:
Example:
Unites States: 73 videos
Here is the Google Colab notebook sample I've created, so anyone can reproduce this issue.
This is the Worldmap chart settings:
# Save chart as svg:
worldmap_chart.render_to_file('worldmap_result.svg',
show_legend=True,
human_readable=True,
fill=True,
print_labels=True,
no_data_text="No data available",
legend_at_bottom=True,
pretty_print=True,
x_title="Hover the cursor on the country's name bellow or the map above:",
print_values=True,
force_uri_protocol='https')
I've readed the documentation and searched, but, I'm not been able to find the correct configuration (if possible) for generate the expected tooltip.
Does anyone knows what I'm missing?

After testing for a few more hours, I finally decided to follow these steps:
Generate/render the world chart in a file - in this case, a .svg file.
Read the generated file in the previous step, find and replace certain svg tags - those that contains the data I don't want in the final chart.
Download the modified file.
The said svg tags are as follows:
Example:
<desc class="value">Russian Federation: 1</desc>
Thanks to this answer, I was able to replace the svg tags and leave them empty. With that change, I was able to generate the world map chart as I required.
This is the full, modified and working code:
import types
# Library import:
from pygal_maps_world.maps import World
from pygal.style import DefaultStyle
worldmap_chart = World()
worldmap_chart.title = 'Channels distribution per country'
#list - source: https://stackoverflow.com/a/26716774/12511801
# Excludes "N/A" values:
countriesData = df_countries.loc[df_countries['Country (channel)'] != "N/A"].to_dict('split')["data"]
# Loop the "countriesData" and replace the name of the country
# with is ISO code - lowercase - in order to render the world map:
for x in countriesData:
countryName = x[0]
countryName = [x["id"].lower() for x in all_countries if x["name"] == countryName][0]
x[0] = countryName
# Strings to replace:
js_strings = []
# Generate world map chart:
#worldmap_chart.add('Countries', dict(countriesData))
for data in countriesData:
countryFound = [x["name"] for x in all_countries if x["id"].lower() == data[0]]
if (len(countryFound) > 0):
countryFound = countryFound[0]
worldmap_chart.add(countryFound, [{"value": data[0], "label" : (str(data[1]) + " video" + ("s" if data[1] != 1 else ""))}])
js_strings.append('<desc class="value">{0}: 1</desc>'.format(countryFound))
# Save chart as svg - comment this line for print the chart in this notebook:
worldmap_chart.render_to_file('chart_final_4.svg',
show_legend=True,
human_readable=True,
fill=True,
print_labels=True,
no_data_text="No data available",
legend_at_bottom=True,
pretty_print=True,
x_title="Hover the cursor on the country's name bellow or the map above:",
print_values=True)
print("Chart saved in local storage - see file (chart_final_4.svg).")
# Render the chart in the notebook:
HTML_worldChart = str(worldmap_chart.render(show_legend=True, human_readable=True,
fill=True, print_labels=True,
no_data_text="No data available",
legend_at_bottom=True,
print_values=True, style=DefaultStyle(value_font_family='googlefont:Raleway',
value_font_size=30,
value_colors=('white',))))
# Source: https://stackoverflow.com/a/62008698/12511801
from pathlib import Path
file = Path("chart_final_4.svg")
# Replace specific, conflicting countries by name:
file.write_text(file.read_text().replace('<desc class="value">Korea, Republic of: 1</desc>', '<desc class="value"></desc>'))
file.write_text(file.read_text().replace('<desc class="value">Russian Federation: 1</desc>', '<desc class="value"></desc>'))
file.write_text(file.read_text().replace('<desc class="value">Viet Nam: 1</desc>', '<desc class="value"></desc>'))
## This is due those countries are arranged as follows:
##--country (<desc class="value">Russia: 1</desc>) no detected
##--country (<desc class="value">Vietnam: 1</desc>) no detected
##--country (<desc class="value">South Korea: 1</desc>) no detected
# Loop the strings to replace:
for country in js_strings:
file.write_text(file.read_text().replace(country, '<desc class="value"></desc>'))
print("(chart_final_4.svg) has been modified.")
Output: world map chart modified - I'm modified the screenshot for include two tooltips at the same time.
Side note: it might not be the best way, but, for a lack of time and knowledge, I've decided to leave it as is. I'm open to any, better suggestions.

Related

Python folium synchronizing 2 markerclusters

I have a situation, where I decided to distinguish my markers slightly by adding the MarkerCluster to the existing Marker.
The clustering works, although isn't synchronized. It means, that if I switch one layer off, just the first markercluster disappears, whereas the second criterion defined as CircleMarker still appears like shown below.
My code is:
df = pd.read_csv("or_geo.csv")
fo=FeatureGroup(name="OR",overlay = True)
openreach_cluster = MarkerCluster(name="OR").add_to(map)
openreach_status = MarkerCluster(control=False,
visible=True
).add_to(map)
for i,row in df.iterrows():
lat =df.at[i, 'lat']
lng = df.at[i, 'lng']
sp = df.at[i, 'sp']
stat = df.at[i,'status']
popup = df.at[i,'sp'] +'<br>' + str(df.at[i, 'street']) + '<br>' + str(df.at[i, 'post code']) + '<br>{}'.format(style)
or_marker = folium.Marker(location=[lat,lng], tooltip='<strong>Job details</strong>', popup=popup, icon = folium.Icon(
color='blue', icon='glyphicon-calendar'))
or_stat_marker = folium.CircleMarker(
location=[lat,lng],
radius=10,
color=or_color(stat),
fill_color=or_color(stat),
fill_opacity=0.5)
openreach_cluster.add_child(or_marker)
openreach_status.add_child(or_stat_marker)
Is there any way to combine these markerClusters together or sync them?
UPDATE:
The first approach from the answer below, unfortunately, doubles the jobs up and the user can't see them until clicks on any of them shown below:
This option would be fantastic if the behavior could be the same as in the image above.
UPDATE II:
The second approach is still not what I am looking for, because the clusters are doubled again and after clicking the circlemarker falls almost in the opposite direction as presented in the image above.
I need to have the behaviors exactly like those displayed on the top-left image. The circlemarker should be integrated with the point marker.
If I got you right, I think there are two possibilities:
Use the same cluster
Add your markers from or_stat_marker to the openreach_cluster and not to another cluster to de-/activate them at the same time with the same button
# was
openreach_cluster.add_child(or_marker)
openreach_status.add_child(or_stat_marker)
# try this
openreach_cluster.add_child(or_marker)
openreach_cluster.add_child(or_stat_marker)
Use marker subgroups
In this case you will have three checkmarks, one parent and two childs for each markercluster which gives full control to visibility
UPDATE: If you add the control=False option to the subgroup you will only see the parent group in LayerControl which then show/hide both groups. But the matter of markers "spreading for visibility" still is a problem I think
Another UPDATE: You are able to deactivate the clustering according to the map zoom level or even at all by using disableClusteringAtZoom option (use True or an int number for zoom level). See here for reference
# markergroups in layercontrol
mc = folium.plugins.MarkerCluster(name='OR',
overlay=True,
control=True,
show=True,
disableClusteringAtZoom=15) # choose zoom lvl to your needs
mc.add_to(map)
sub1 = folium.plugins.FeatureGroupSubGroup(mc, name='openreach_cluster', control=False, show=True) # False --> deactivated at start
sub1.add_to(map)
sub2 = folium.plugins.FeatureGroupSubGroup(mc, name='openreach_status', control=False)
sub2.add_to(map)
# the layercontrol itself
lc = folium.map.LayerControl(collapsed=False)
lc.add_to(map)
# ...
for i, row in df.iterrows():
# ...
or_marker = folium.Marker(...)
or_marker.add_to(sub1)
or_stat_marker = folium.CircleMarker(...)
or_stat_marker.add_to(sub2)
My Result:
By following the second approach described I get a map shown here. There is one checkmark "Segment Markers" which shows/hides the markers including the circles. They don't move around when clicked and are fully shown when zoomed in to a specific lvl by using disableClusteringAtZoom.
Sorry this didn't work for your problem, I really don't know why ..

extract .dxf data using ezdxf to add to a pandas dataframe

The end goal is to extract the text contained on a specific layer, inside of a named view, from the model space. I have the layer restriction (the text in yellow for visual), but I can't seem to figure out the syntax (if possible) to limit the query to either items inside one of the named views, or within a bounding box that I define to match the named view (orange box). The text being queried is single text. It is (and will always be) an exploded table. Each text item has a unique .insert value.
Ultimately this above mentioned query loop would be put inside a loop to iterate over all named views (bounding box) inside of the model space. Each of the iterations creating a unique list containing the query results. The list would then be input into a pandas dataframe for further manipulations.
import ezdxf
filepath = "C:/MBS_JOBS-16/8741-22/8741-22_(DTL).dxf"
doc = ezdxf.readfile(filepath)
msp = doc.modelspace()
ls = []
for e in msp.query('TEXT MTEXT[layer=="text"]'):
ls.append(e.dxf.text)
print(ls)
The ezdxf package does not have a feature for selecting entities based on location and size, but a bounding box based implementation is relatively easy.
It is important to know that bounding boxes of text based entities (TEXT, MTEXT) are inaccurate because matplotlib (used by ezdxf to render text) renders TTF fonts differently than AutoCAD.
I created an example DXF file with 6 views, called "v1", "v2", ...:
The following code prints the the content of view "v1": ["Text1a", "Text1b"]
import ezdxf
from ezdxf.math import BoundingBox, Vec2
from ezdxf import bbox
def get_view_box(view):
center = Vec2(view.dxf.center)
size = Vec2(view.dxf.width, view.dxf.height)
bottom_left = center - (size / 2)
top_right = center + (size / 2)
return BoundingBox([bottom_left, top_right])
doc = ezdxf.readfile("views.dxf")
msp = doc.modelspace()
view = doc.views.get("v1")
view_box = get_view_box(view)
for e in msp.query("TEXT MTEXT"):
text_box = bbox.extents([e]) # expects a list of entities!
if view_box.contains(text_box):
print(e.dxf.text)

Getting ipywidgets to update a folium heatmap?

Beginner python user, having some success with heatmaps using Folium. I am experimenting with using the Global Terrorism Database as a source for some visualizations but I really wanted to use ipywidgets to select a specific terrorist group from a list to update my heatmap. I've already constructed a heatmap for ISIS, created an ipyvuetify button containing the groups I want to compare, but am having trouble figuring out the function and widgets.link syntax. I'd really appreciate some help because there don't seem to be any good guides (for my skill level) on how to do what I'm trying to do here.
import ipyvuetify as v
import ipywidgets as widgets
import folium
from folium import plugins
from folium.plugins import HeatMap as hm
map = folium.Map(location = [25,15], tiles = "cartodbdark_matter", zoom_start = 2)
selected = gtd.loc[gtd['Group'] == 'Islamic State of Iraq and the Levant (ISIL)']
coords = selected[['Latitude','Longitude']]
hm(coords).add_to(map)
terrorists = [
'Taliban',
'Shining Path (SL)',
'Islamic State of Iraq and the Levant (ISIL)',
'Farabundo Marti National Liberation Front (FMLN)',
'Al-Shabaab',
'Irish Republican Army (IRA)',
'Revolutionary Armed Forces of Colombia (FARC)',
'New Peoples Army (NPA)',
'Kurdistan Workers Party (PKK)',
'Boko Haram']
# This is where things go off the rails for me, not exactly sure how to arrange this function
def update(x):
if widget_terrorist_selector.selected is not None:
xmin, xmax = widget_terrorist_selector.selected
widget_terrorist_selector.observe(update,'widget_terrorist_selector')
# The selector here works but I can't figure out how to link it to my map so that it pushes a new set of heatmap coordinates
widget_terrorist_selector = v.Select(label='Choose Option', items=terrorists, v_model=terrorists[0])
widget_terrorist_selector
# This bit keeps throwing a "TypeError: Each object must be HasTraits, not <class 'pandas.core.frame.DataFrame'>"
widgets.link((widget_terrorist_selector,'v_model'),(selected, 'Group'))
Thanks in advance!
widgets.link is used for keeping two ipywidgets in sync. I'm not sure that's what you're trying to achieve here, it can probably be achieved with plain old observe.
I think in your update function you need to make the necessary change to your map, something like
def update(x):
new_choice = widget_terrorist_selector.selected
selected = gtd.loc[gtd['Group'] == new_choice]
coords = selected[['Latitude','Longitude']]
hm(coords).add_to(map)

Python - Folium Search Plugin Doesn't Search Popup Text in MarkerCluster Group Layer

My folium map, marker cluster, layer control, and search bar all work and show up correctly except for actually using the search bar. The layer I have the search plugin point to when searching is my MarkerCluster layer, which the folium documentation says should be searchable. Anything I type into the search bar it returns "Location not found" which makes me think it's not searching through the MarkerCluster layer correctly, or does not know to search through the text of that layer that is included in the markers' popups.
Here is my code as I have it right now:
import os
import folium
from folium import plugins
import pandas as pd
from folium.plugins import MarkerCluster
from folium.plugins import Search
#import data including lat/lon, name of service, address of service
df = pd.read_csv('data.csv')
#Create base map zoomed in to Indiana
map=folium.Map(location=[39.80, -86.12], tiles=None, zoom_start=7)
folium.TileLayer('cartodbpositron', name='COVID-19 Services').add_to(map)
#Make Marker Cluster Group layer
mcg = folium.plugins.MarkerCluster(control=False)
map.add_child(mcg)
#Create layer of markers
#Set marker popups to display name and address of service
for row in df.iterrows():
row_values=row[1]
location=[row_values['latitude'], row_values['longitude']]
popup=popup=(row_values['name']+'<br>'+'<br>'+row_values['address_1']+
'<br>'+'<br>'+row_values['city']+','+' '+row_values['state']+
'<br>'+row_values['zip'])
marker=folium.Marker(location=location, popup=popup, min_width=2000)
marker.add_to(mcg)
#Add search bar
servicesearch = Search(
layer=mcg,
geom_type='Point',
placeholder='Search for a service',
collapsed=False,
).add_to(map)
#Add layer control
folium.LayerControl().add_to(map)
map
How do I get the search plugin to actually search the test of the marker popups? Then, how do I get the map to highlight or zoom to those searched for markers? Any help is GREATLY appreciated, thank you!
This is my solution:
def visualize_locations_with_marker_cluster(df, zoom=4):
f = folium.Figure(width=1000, height=500)
center_lat=34.686567
center_lon=135.52000
m = folium.Map([center_lat,center_lon], zoom_start=zoom).add_to(f)
marker_cluster = MarkerCluster().add_to(m)
for d in df.iterrows():
folium.Marker(location=[d[1]["y"], d[1]["x"]], popup=d[1]["company"], name=d[1]["company"]).add_to(marker_cluster)
servicesearch = Search(
layer=marker_cluster,
search_label="name",
placeholder='Search for a service',
collapsed=False,
).add_to(m)
return m
First create map, create cluster, loop values in pd.dataframe and create Marekers for Cluster with name label.
Next create Search object and add cluster there with search_label="name", label. Finally add it all back to the map
["x", "y"] is longtitude and latitude, company is a search value in my case
If you change a little how you add data to the map, it will be easier to use the search bar. You can change the data in your dataframe into a GeoJSON objet. You first need to create a dictionnary for your GeoJSON and use the function folium.GeoJSON() :
geo_json = {
"type": "FeatureCollection",
"features": [],
}
for d in df.iterrows():
temp_dict = {
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates":[d[1]["longitude"], d[1]["latitude"]],
},"properties": {"name": d[1]["name"]}
}
geo_json["features"].append(temp_dict)
geojson_obj = folium.GeoJson(geo_json).add_to(map)
After that, you just need to change a little your code to add the search bar :
servicesearch = Search(
layer=geojson_obj,
search_label="name",
placeholder='Search for a service',
collapsed=False,
).add_to(map)
I have been struggling a while with the same. I believer this might fix it: add a 'name' or whatever you want to be searched to the marker, e.g.:
marker=folium.Marker(location=location, popup=popup, min_width=2000, name=name)
A little late, but better than never, perhaps.

How can I highlight multiple search results in Python folium/leaflet?

I have plotted graph in Python using folium/Leaflet with search. Problem I am facing is that it highlights only one result on map even if there are multiple results.
For example if I search by name then it works fine as mostly there is only 1 person with that full name.
But if I search on a city for example 'Delhi' then it results in highlighting only 1 marker instead of 7 or 10.
In the image below: There is only 1 marker circled in red which it is pointing as result but there others from same city which are not highlighted.
And how can I change highlighting properties to something more noticeable?
Image:
Here is the code:
######################################################################
# Part 1 - Creating map
m_search = folium.Map(location=[28.6003435, 77.21952300000001],zoom_start=11
)
#######################################################################
# Part 2 - Creating folium markers on map with html text, images
# etc dynamically for multiple points using for-loop
for plot_numb in range(gdf.shape[0]):
icon = folium.Icon(color="blue", icon="cloud", icon_color='white')
tooltip = 'Click to view more about: '+gdf.iloc[plot_numb,0]
var_name = gdf.iloc[plot_numb,0]
var_loc = gdf.iloc[plot_numb,2]
pic = base64.b64encode(open('Images/'+gdf.iloc[plot_numb,5],'rb').read()).decode()
html = f'''<img ALIGN="Right" src="data:image/png;base64,{pic}">\
<strong>Name: </strong>{var_name}<br /><br />\
<strong>Location: </strong>{var_loc}<br /><br />\
'''
html
iframe = IFrame(html, width=300+180, height=300)
popup = folium.Popup(iframe, max_width=650)
marker = folium.Marker(location=gdf.iloc[plot_numb,1], popup=popup, tooltip=tooltip, icon=icon).add_to(m_search)
########################################################################
# Part 3 - Creating Markers using GeoJson, creating search
# creating folium GeoJson objects from out GeoDataFrames
pointgeo = folium.GeoJson(gdf,name='group on map', show=False,
tooltip=folium.GeoJsonTooltip(fields=['Name', 'Relation', 'City'], aliases=['Name','Relation', 'City'],
localize=True)).add_to(m_search)
# Add some Search boxes to the map that reference the GeoDataFrames with some different parameters passed to the
# arguments.
pointsearch = Search(layer=pointgeo, geom_type='Point',
placeholder='Search for contacts', collapsed=False,
search_label='Name').add_to(m_search)
# To Add a LayerControl add below line
folium.LayerControl().add_to(m_search)
m_search
Below is the example of the gdf Data Frame:
Name Location City Relation Relation Detail Image Lat Lon geometry
0 abc [28.562193, 77.387073] Noida Cousin Cousin 1.png 28.562193 77.387073 POINT (77.387073 28.562193)
1 def [28.565282027743955, 77.44913935661314] Noida Cousin Cousin Brother 2.png 28.565282 77.449139 POINT (77.44913935661314 28.56528202774395)
3 ghi [28.6206996683473, 77.42576122283936] Noida Cousin Cousin Brother 4.png 28.620700 77.425761 POINT (77.42576122283936 28.6206996683473)
Need some help here as I am new to coding and unable to figure out.

Categories

Resources