Bokeh - get information about points that have been selected - python

I have a several points that I plot into scatter plot and show in web browser window (using Bokeh).
For selection, I use PolySelectTool or BoxSelectTool.
There are two things I would like to do:
1) Get information about points that have been selected in order to calculate some additional information.
2) As points represent URLs, I would like the chart to open a new browser tab and load a particular URL whenever I click a point (representing URL).
I don't think the code is important. But to make my question complete, here it is ...
Y = my_data
urls = get_urls(my_data)
TOOLS="pan,wheel_zoom,reset,hover,poly_select,box_select"
p = figure(title = "My chart", tools=TOOLS)
p.xaxis.axis_label = 'X'
p.yaxis.axis_label = 'Y'
source = ColumnDataSource(
data=dict(
xvals=list(Y[:,0]),
yvals=list(Y[:,1]),
url=urls
)
)
p.scatter("xvals", "yvals",source=source,fill_alpha=0.2, size=5)
hover = p.select(dict(type=HoverTool))
hover.snap_to_data = False
hover.tooltips = OrderedDict([
("(x,y)", "($x, $y)"),
("url", "#url"),
])
select_tool = p.select(dict(type=BoxSelectTool))
#
# I guess perhaps something should be done with select_tool
#
show(p)

You can get information with the source.selected property, if you want to be notified of every change you must create a callback, it would be something like this:
def callback(obj, attr, old, new):
...
source.on_change('selected', callback)
See this example for more details.

Related

Dash chart does not display default dropdown value selection upon page load and refresh

I'm just getting to grips with Dash but am coming across a problem I can't resolve. I am trying to pass a default dropdown value to a line chart upon page load, but even though the value is present in the dropdown menu it doesn't render in the chart:
As soon as I select another country (region) though, the chart works as expected:
How do I get the chart to load with my default dropdown value (England)? Here's the code:
df_prices = pd.read_csv('Average-prices-2022-01.csv')
df_prices["Date"] = pd.to_datetime(df_prices["Date"])
app = JupyterDash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
app.layout = html.Div(children=[html.H1('My Dash App'), html.Br(),
html.Div(children=[html.H3('Select Country'), dcc.Dropdown(id='region_picker', options=[{"label":region, "value": region} for region in df_prices["Region_Name"].unique()], value='England' ,multi=True, clearable=True, style={'text-align':'left'})], style={'display':'inline-block', 'vertical-align':'top', 'width':'25%', 'padding-right': '5px'}),
html.Div(children=[html.H3("House Prices", style={'padding-left':'30px'}), dcc.Graph(id="price_chart")], style={'display':'inline-block', 'width':'75%'})], style={'margin': '50px'})
#app.callback(
Output(component_id="price_chart", component_property="figure"),
Input(component_id="region_picker", component_property="value")
)
def update_region(input_country):
#input_country = 'England'
data = df_prices.copy(deep=True)
data = data[data['Region_Name'].isin(list(input_country))]
line_chart = px.line(data_frame = data, x="Date", y="Average_Price", title=f'Average House Price in {input_country}',color='Region_Name', render_mode="webgl")
return line_chart
Thanks!
The 'value' attribute of the dropdown component has to be a list instead of string while setting multi as True. Just wrap the square brackets around the value as follows and you should be good to go:
dcc.Dropdown(id='region_picker', options=[{"label":region, "value": region} for region in df_prices["Region_Name"].unique()], value=['England'] ,multi=True, clearable=True, style={'text-align':'left'})

Folium - add larger pop ups with data from XML file

I would like to create a table-like pop-up for my folium map but don't know how to do it (I'm a novice).
My data comes from an XML file that contains the gps coordinates, name, sales, etc. of stores.
Right now I can display the name of the stores in the pop-up, but I would also like to display the sales and other information below the name.
I reckon I should maybe use GeoJson but I don't know how to implement it in the code I already have (which contains clusterization) :
xml_data = 'Data Stores.xml'
tree = ElementTree.parse(xml_data)
counter = tree.find('counter')
name = counter.find('Name')
counter.find('Latitude').text
name = []
latitude = []
longitude = []
for c in tree.findall('counter'):
name.append(c.find('Name').text)
latitude.append(c.find('Latitude').text)
longitude.append(c.find('Longitude').text)
df_counters = pd.DataFrame(
{'Name' : name,
'Latitude' : latitude,
'Longitude' : longitude,
})
df_counters.head()
locations = df_counters[['Latitude', 'Longitude']]
locationlist = locations.values.tolist()
map3 = folium.Map(location=[31.1893,121.2781], tiles='CartoDB positron', zoom_start=6)
marker_cluster = folium.plugins.MarkerCluster().add_to(map3)
for point in range(0, len(locationlist)):
popup=folium.Popup(df_counters['Name'][point], max_width=300,min_width=300)
folium.Marker(locationlist[point],
popup=popup,
icon=folium.Icon(color='blue', icon_color='white',
icon='fa-shopping-bag', angle=0, prefix='fa')
).add_to(marker_cluster)
map3.save("WorldMap.html")`
Right now I have 4 other columns in my XML file besides 'Name' that have the information that I want to appear in the popup as well, kinda like this :
example popup
Thank you for your help
Edit :
I did some digging and changed my code a little bit by adding the folium.features.GeoJsonPopup instead of the simple folium.Popup that I had before :
for point in range(0, len(locationlist)):
popup=folium.features.GeoJsonPopup(
fields=[['Name'],['Opening']],
aliases=['Name','Opening'])
folium.Marker(locationlist[point],
popup=popup,
icon=folium.Icon(color='blue', icon_color='white',
icon='fa-shopping-bag', angle=0, prefix='fa')
).add_to(marker_cluster)
I added the 'Opening' data, however I don't know how to transfer it into the pop up along with the 'Name' since it comes from a panda DataFrame. Right now my popups are empty.
I have done something similar, steps were:
create an IFrame with the content you want to display (coded in HTML)
use this IFrame in a popup
connect this popup with your marker
htmlstr = ... # Here you can add your table, use HTML
# 1. iframe
iframe = folium.IFrame(htmlstr, # places your content in the iframe
width=200,
height=200 # adjust size to your needs
)
# 2. popup
fpop = folium.Popup(iframe)
# 3. marker
mrk = folium.Marker(location=latlng,
popup=fpop,
)
mrk.add_to( ... )

Center-align title in Pygal chart

I'm making a line chart in Pygal. I want to center the title of the chart, but I don't know how to do that.
I tried looking through the Pygal documentation, but I couldn't find anything relating to the alignment of a title. Here's what I have:
custom_style = Style(
background = 'transparent',
font_family='Avenir',
colors = ['#14A1FF', '#14FF47'],
opacity = .5)
chart = pygal.Line(
style=custom_style,
print_values=True,
interpolate='hermite',
fill=True, dots_size=4,
show_y_guides=False,
legend_at_bottom=True,
legend_at_bottom_columns=2)
chart.title = "Rubik's Cube Solve Times Recorded Over Five Days"
chart.x_labels = ["1", "2", "3", "4", "5"]
chart.x_title = "Day"
chart.y_title = "Seconds"
chart.add("Average of 100", ao100)
chart.add("Average of 25", ao25)
chart.render_to_file('times.svg')
As mentioned in the comments the figure title is centred relative to the figure, rather than axes. This behaviour is hard-coded in the rendering functions, there are no configuration options that will change it.
One workaround is to create your own class that inherits from pygal.Line and over-rides the function that renders the title (which isn't very large):
class MyLineChart(pygal.Line):
def __init__(self, *args, **kwargs):
super(MyLineChart, self).__init__(*args, **kwargs)
def _make_title(self):
"""Make the title"""
if self._title:
for i, title_line in enumerate(self._title, 1):
self.svg.node(
self.nodes['title'],
'text',
class_='title plot_title',
x=self.margin_box.left + self.view.width / 2, # Modified
y=i * (self.style.title_font_size + self.spacing)
).text = title_line
The _make_title function above was copied straight from the source code for the Graph class (the class that Line itself inherits from). The only change is in the line indicated with the comment 'Modified', this was taken from the function that renders the x axis label (because that is centred on the axes).
With this you can replace chart = pygal.Line with chart = MyLineChart, but leave the rest of the code as it is. You might also want to change the name of the class to something more meaningful.
By default you title has has property text-anchor:middle:
text-anchor attribute is used to align (start-, middle- or
end-alignment) a string of text relative to a given point.
You can manually change this value, .i.e., to end in finale svg file (open file in text editor and find .title ).

Python Folium MarkerCluster Color Customization

I'm creating a leaflet map in folium using MarkerCluster. I have been all over the documentation and searched for examples, but I cannot figure out how to customize the color for a given MarkerCluster or FeatureGroup (e.g., one set in green rather than default blue).
I tried creating the markers individually and iteratively adding them to the MarkerCluster, and that gave me the color I wanted, but then the iFrame html table woudn't function properly, and the popups were not appearing.
The code I've written works flawlessly (an html table used for popups is not supplied), but I'd really like to be able to change the color for one set of markers and retain the popups using the methods in my code. Any guidance would be greatly appreciated!
or_map = folium.Map(location=OR_COORDINATES, zoom_start=8)
res_popups, res_locations = [], []
com_popups, com_locations = [], []
for idx, row in geo.iterrows():
if row['Type'] == 'Residential':
res_locations.append([row['geometry'].y, row['geometry'].x])
property_type = row['Type']
property_name = row['Name']
address = row['address']
total_units = row['Total Unit']
iframe = folium.IFrame(table(property_type, property_name,
address, total_units), width=width,
height=height)
res_popups.append(iframe)
else:
com_locations.append([row['geometry'].y, row['geometry'].x])
property_type = row['Type']
property_name = row['Name']
address = row['address']
total_units = row['Total Unit']
iframe = folium.IFrame(table(property_type, property_name, address,
total_units), width=width,
height=height)
com_popups.append(iframe)
r = folium.FeatureGroup(name='UCPM Residential Properties')
r.add_child(MarkerCluster(locations=res_locations, popups=res_popups))
or_map.add_child(r)
c = folium.FeatureGroup(name='UCPM Commercial Properties')
c.add_child(MarkerCluster(locations=com_locations, popups=com_popups))
or_map.add_child(c)
display(or_map)
Instead of just dumping all your locations into the Cluster, you could loop over them and create a Marker for each of them - that way you can set the Marker's color. After creation, you can add the Marker to the desired MarkerCluster.
for com_location, com_popup in zip(com_locations, com_popups):
folium.Marker(com_location,
popup=com_popup
icon=folium.Icon(color='red', icon='info-sign')
).add_to(cluster)
A different approach would be to modify the style function, as shown here (In[4] and In[5]).

Display and update text with Bokeh: settings glyph on update not working

I am trying to write a web app that displays graphs with Bokeh and also displays formatted text retrieved from an API. The display dynamically changes with the content of the graph when the user clicks a button. Is there a standard way to format and present text, and then update it, with Bokeh?
I thought I could do this with text glyphs, but so far I am failing. Here's what I'm trying to do for the update action:
def update_graph(attrname, old, new):
USER_ID = new
use_df = get_user_df(new, cur, date_from, date_to)
plot1.title = get_api_output(new, INSIGHT_DATE)
textplot.text(3, 3, text=["hellllllo"], text_color="firebrick", text_align="center", text_font_size="20pt")
source1.data['x'] = use_df[use_df['type'] == 0]['date'].values
source1.data['y'] = use_df[use_df['type'] == 0]['value'].values
This mostly works fine the way it is. I grab some data from a remote DB with get_user_df and update source1, which works appropriately. Similarly the title gets updated as desired.
However no text glyph appears on textplot and no error appears in the console. What's going on and how can I dynamically adjust text glyphs on a plot?
Also here is the code I use to set up the plots in case that is relevant:
#plot utilities
def mtext(p, x, y, text):
p.text(x, y, text=[text],
text_color="firebrick", text_align="center", text_font_size="10pt")
#PLOTS
textplot = Figure(toolbar_location = None, title = 'API text', title_text_font_size='8pt')
mtext(textplot, randint(2, 5), randint(2, 5), 'annotation')
plot1 = Figure(toolbar_location = None, title = 'Distance (k)', title_text_font_size='8pt')
source1 = ColumnDataSource(data = dict(x = use_df[use_df['type'] == 0]['date'].values, y = use_df[use_df['type'] == 0]['value'].values))
plot1.circle('x', 'y', line_color=None, source = source1, color = 'pink', size = 20)

Categories