Plotly Choropleth_mapbox plots with Dash interactivity - python-3.x

I am a GIS person fairly new to Plotly and exceptionally new to Dash. I'm trying to mostly copy an example solution from a post here:
drop down menu with dash / plotly
To build an interactive app to look at various choropleth maps based on choropleth_mapbox figures. The last solution from the above post, using Plotly and Dash by Rob Raymond, looks brilliant and close to what I am trying to do. But in my case, my figures built on several data 'columns' also require an individual update_layout call and a hovertemplate built for each data column; and I cannot figure out where to place those definitions within the solution posted above.
This is my code for a single data column's figure, which gives me the functionality I want in the layout and hover tool:
fig = px.choropleth_mapbox(
gdf_blks_results,
geojson = gdf_blks.geometry,
locations = gdf_blks_results.index,
color=classme.yb,
color_continuous_scale = "YlOrRd",
center={"lat": 18.2208, "lon": -66.49},
mapbox_style="open-street-map",
width=800,
height=500,
custom_data = [gdf_blks_results['GEOID'],
gdf_blks_results['overallBurden']]
)
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0},
coloraxis_colorbar=dict(
title="burden",
thicknessmode="pixels",
lenmode="pixels",
yanchor="top",y=1,
ticks="outside",
tickvals=[0,1,2,3,4],
ticktext=myclasses,
dtick=5
))
# hover template
hovertemp = '<i>Census ID :</i> %{customdata[0]}<br>'
hovertemp += '<i>burden : </i> %{customdata[1]:.5f}<br>'
fig.update_traces(hovertemplate=hovertemp)
fig.show()
My question is, how do I incorporate that into the list of figures for a set of columns of data with custom template and figure update info for each? I tried to add it to the figure definitions in the cited post example before the "for c, color in zip(...)" statement, but I cannot get the syntax right, and I am not sure why not.

I think you should create a Dropdown list with Options as gdf_blks_results columns the returns it with callback to update choropleth map. Please refer below code:
import pandas as pd
import numpy as np
import plotly.express as px
import dash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import dash_table
import dash_bootstrap_components as dbc
columns_list = list(gdf_blks_results.columns)
app = dash.Dash(__name__,external_stylesheets=[dbc.themes.LUX])
app.layout = html.Div([
dbc.Row([
dbc.Col([
html.H5('Columns',className='text-center'),
],width={'size':2,"offset":0,'order':1}),
dbc.Col([
dcc.Dropdown(id='columns',placeholder="Please select columns",
options=[{'label':x,'value':x} for x in columns_list],
value=[],
multi=False,
disabled=False,
clearable=True,
searchable=True),
],width={'size':10,"offset":0,'order':1})
], className='p-2 align-items-stretch'),
dbc.Row([
dbc.Col([
dcc.Graph(id="choropleth_maps",figure={},style={'height':500}), #Heatmap plot
],width={'size':12,'offset':0,'order':2}),
]),
])
#app.callback(Output('choropleth_maps', 'figure'),
[Input('columns', 'value')])
def update_graph(columns):
fig = px.choropleth_mapbox(
gdf_blks_results,
geojson = gdf_blks.geometry,
locations = gdf_blks_results.index,
color=columns,
color_continuous_scale = "YlOrRd",
center={"lat": 18.2208, "lon": -66.49},
mapbox_style="open-street-map",
width=800,
height=500,
custom_data = [gdf_blks_results['GEOID'],
gdf_blks_results['overallBurden']])
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0},
coloraxis_colorbar=dict(
title="burden",
thicknessmode="pixels",
lenmode="pixels",
yanchor="top",y=1,
ticks="outside",
tickvals=[0,1,2,3,4],
ticktext=myclasses,
dtick=5
))
# hover template
hovertemp = '<i>Census ID :</i> %{customdata[0]}<br>'
hovertemp += '<i>burden : </i> %{customdata[1]:.5f}<br>'
fig.update_traces(hovertemplate=hovertemp)
return fig
if __name__ == "__main__":
app.run_server(debug=False,port=1116)

Related

Not able to update the plot on selection of a value of Select widget of Bokeh Library

I am working on COVID19 analysis and am using a JSON data source. I have converted the json to dataframe. I am working on plotting a daily case, daily death and daily recovered bar chart over a datetime x-axis for each state and the state can be selected using a Select widget. I don't know Javascript so, I am trying to avoid using Javascript callbacks but have been using a function to update the select.value. I am not sure why is the plot not getting updated even when i am running the code on Bokeh server and there are no exceptions raised by the interpreter.
Can someone provide me with any direction or help with what might be causing the issue as I am new to Python and any help is appreciated? Or if there's any other alternative. This code is a derivation from a similar plot on [bokeh discourse][1]
#Creating DataFrame
cases_summary = requests.get('https://api.rootnet.in/covid19-in/stats/history')
json_data = cases_summary.json()
cases_summary=pd.json_normalize(json_data['data'], record_path='regional', meta='day')
cases_summary['day']=pd.to_datetime(cases_summary['day'])
cases_summary['daily deaths']=cases_summary['deaths'].groupby(cases_summary['loc']).diff(1)
cases_summary['daily confirmed']=cases_summary['totalConfirmed'].groupby(cases_summary['loc']).diff(1)
cases_summary['daily discharged']=cases_summary['discharged'].groupby(cases_summary['loc']).diff(1)
#Initializing the first default plot
cases=cases_summary[cases_summary['loc']=='Delhi']
source=ColumnDataSource(data=cases)
a = figure(plot_width=1200, plot_height=700, sizing_mode="scale_both", x_axis_type='datetime')
def make_plot(cases_val):
a.vbar('day', top='daily confirmed', width=timedelta(days=0.5),
legend_label='Daily Confirmed', color='#5e4fa2', source=cases_val)
a.vbar('day', bottom='daily discharged', width=timedelta(days=0.5),
legend_label='Daily Recovered', color='#66c2a5', source=cases_val)
a.vbar('day', bottom='daily deaths', width=timedelta(days=0.5),
legend_label='Daily Deaths', color='#3288bd', source=cases_val)
return a
def update_plot(attr,old,new):
location=select.value
data_loc = cases_summary[cases_summary['loc'] == location]
source = ColumnDataSource(data=dict()).from_df(data_loc)
layout.children[0]=make_plot(source)
select = Select(title="Select State:", value="Delhi", options=cases_summary['loc'].unique().tolist())
plot = make_plot(cases)
controls = column(select)
layout = row(a, controls)
select.on_change('value', update_plot)
curdoc().add_root(layout)
[1]: https://discourse.bokeh.org/t/how-to-update-the-bar-chart-that-has-dataframe-as-source-with-bokeh-select-widget/2031/8
This can be done more simply using a view and a filter. Here is an alternative approach:
import requests
import pandas as pd
from bokeh.plotting import figure
from bokeh.layouts import column, row
from bokeh.io import curdoc
from bokeh.models import *
from datetime import timedelta
cases_summary = requests.get("https://api.rootnet.in/covid19-in/stats/history")
json_data = cases_summary.json()
cases_summary = pd.json_normalize(json_data["data"], record_path="regional", meta="day")
cases_summary["day"] = pd.to_datetime(cases_summary["day"])
cases_summary["daily deaths"] = (
cases_summary["deaths"].groupby(cases_summary["loc"]).diff(1)
)
cases_summary["daily confirmed"] = (
cases_summary["totalConfirmed"].groupby(cases_summary["loc"]).diff(1)
)
cases_summary["daily discharged"] = (
cases_summary["discharged"].groupby(cases_summary["loc"]).diff(1)
)
source = ColumnDataSource(cases_summary)
filter = GroupFilter(column_name='loc',group='Delhi')
view = CDSView(source=source, filters = [filter])
a = figure(
plot_width=1200, plot_height=700, sizing_mode="scale_both", x_axis_type="datetime"
)
a.vbar(
"day",
top="daily confirmed",
width=timedelta(days=0.5),
legend_label="Daily Confirmed",
color="#5e4fa2",
source=source,
view = view
)
a.vbar(
"day",
bottom="daily discharged",
width=timedelta(days=0.5),
legend_label="Daily Recovered",
color="#66c2a5",
source=source,
view = view
)
a.vbar(
"day",
bottom="daily deaths",
width=timedelta(days=0.5),
legend_label="Daily Deaths",
color="#3288bd",
source=source,
view = view
)
def update_plot(attr, old, new):
view.filters = [GroupFilter(column_name='loc',group=select.value)]
select = Select(
title="Select State:", value="Delhi", options=cases_summary["loc"].unique().tolist()
)
controls = column(select)
layout = row(a, controls)
select.on_change("value", update_plot)
curdoc().add_root(layout)

How to link axes of all plots in a Bokeh layout?

I am designing a Bokeh layout using the Bokeh server. I am defining two main columns (see attached image), and I am attempting to link the x-axis of all plots on the right column. The problems are that:
I am trying to make this app as dynamic as possible, which mean that depending on the case-study, not all the plots will be available, and each individual plot is set from a separate function
Each plot object is stored in a list, and I don't know how to access their properties
The reference plot is not known a priori so I don't see how I can implement the example in the Bokeh doc - in other words, I need to first plot all the subplots to then get the relevant x_range
So I was wondering if it is possible to set the linking behaviour a posteriori once all plots in the column are defined (i.e. the output of plotDataset below). My intuition is to loop through the objects, get the children and set the x_range to the first plot, but I don't know how to do that.
Below is a simplified version of what I am trying to achieve. Ideally, I would get the x_range of the first plot of fCol and apply it to all other plots just before return column(fCol)
Any idea is greatly appreciated! And also, I am fairly beginner with Python so please shout if you see anything else horrible!
Thank you
def plotTS(data, col):
tTmp = []
# A loop that defines each tab of the plot
for i in range(len(col)):
fTmp = figure()
fTmp.circle(data[:]['time'], data[:][col[i]], color=color)
# Append tab
tTmp.append(Panel(child=fTmp))
# Return the tabs
return Tabs(tabs=tTmp)
def plotDataset(data):
col = ['NDVI', 'EVI'] # Name of the tabs
fCol = []
fCol.append(plotTS(data, col))
# NOTE: I use an append approach because in reality plotTS is called more than once
return column(fCol)
# General layout - I did not include the code for the left column
layout = row(leftColumn, plotDataset(data))
Link to image
See code below (Bokeh v1.1.0).
from bokeh.models import Panel, Tabs, Column, Row
from bokeh.plotting import figure
from tornado.ioloop import IOLoop
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
def modify_doc(doc):
leftColumn = Column(figure())
def plotTS(data, col):
tTmp = []
for i in col:
fTmp = figure()
fTmp.circle(data['x'], data['y'], color='black')
tTmp.append(Panel(child=fTmp, title = i))
return Tabs(tabs=tTmp)
def plotDataset(data):
col = ['NDVI', 'EVI']
fCol = plotTS(data, col)
shared_range = None
for panel in fCol.tabs:
fig = panel.child
if shared_range is None:
shared_range = fig.x_range
else:
fig.x_range = shared_range
return Column(fCol)
layout = Row(leftColumn, plotDataset(data = dict(x = [1, 2, 3], y = [1, 2, 3])))
doc.add_root(layout)
io_loop = IOLoop.current()
server = Server(applications = {'/app': Application(FunctionHandler(modify_doc))}, io_loop = io_loop, port = 5002)
server.start()
server.show('/app')
io_loop.start()

How can I handle all my subplot graphs just using a single slider? [Python-Dash]

I am using a subplot structure for my plots and I want to range in the x-axis by using a Slider. I try to range through my graphs simultaneously, but it doesn't work. The callback routine destroys the subplot structure (created with plotly.tools.make_subplots).
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from plotly import tools
from plotly import graph_objs as go
fig = tools.make_subplots(rows=2, shared_xaxes=True)
fig.add_scatter(x=[1,2,3], y=[2,1,2])
fig.add_scatter(x=[1,2,3], y=[5,3,3], yaxis='y2')
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div(children=[
dcc.Graph(
id='graph1',
figure = fig
),
dcc.Slider(
id='temp-slider',
min=0,
max=10,
step=0.2,
value=0
)
])
#app.callback(
Output('graph1','figure'), [Input('temp-slider','value')]
)
def update_graph(value):
out = dict(
data = fig.data,
layout = dict(
xaxis = dict(
range = [value,value+1]
)
)
)
return out
if __name__ == '__main__':
app.run_server(debug=True)
I need a subplot that ranges both graphs by using the same slider
Since you are recreating the graph again, the subplot layout is lost, please just update the specific range property of the existing figure instead. Please refer the below example code and let me know if this fixes your issue.
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
from plotly import tools
from plotly import graph_objs as go
fig = tools.make_subplots(rows=2, shared_xaxes=True)
fig.add_scatter(x=[1,2,3], y=[2,1,2])
fig.add_scatter(x=[1,2,3], y=[5,3,3], yaxis='y2')
fig.layout.xaxis.dtick=0.5
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div(children=[
dcc.Graph(
id='graph1',
figure = fig
),
dcc.Slider(
id='temp-slider',
min=0,
max=10,
step=0.2,
value=0
)
])
#app.callback(
Output('graph1','figure'), [Input('temp-slider','value')]
)
def update_graph(value):
fig.layout.xaxis.range = [value,value+1]
return fig
if __name__ == '__main__':
app.run_server(debug=True)

Dash python plotly live update table

I am new to plotly dash. I want to draw a table whose values (Rows) will
automatically be updated after certain interval of time but i do not know how
to use dash table experiments. The table is already saved as CSV file but i
am somehow unable make it live.
Please help!
Can some one guide me in the right direction what should i do
Your help will be highly appreciated. Following is the code.
import dash
import pandas as pd
from pandas import Series, DataFrame
from dash.dependencies import Input, Output, Event
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dtable
app=dash.Dash()
def TP_Sort():
address = 'E:/Dats Science/POWER BI LAB DATA/PS CORE KPIS/Excel Sheets/Throughput.xlsx'
TP = pd.read_excel(address)
TP1=TP.head()
Current_Interval.to_csv('TP1.csv', index=False)
return app.layout = html.Div([
html.H1('Data Throughput Dashboard-NOC NPM Core'),
dcc.Interval(id='graph-update',interval=240000),
dtable.DataTable(id='my-table',
rows=[{}],
row_selectable=False,
filterable=True,
sortable=False,
editable=False)
])
#app.callback(
dash.dependencies.Output('my-table','row_update'),
events=[dash.dependencies.Event('graph-update', 'interval')])
def update_table(maxrows=4):
TP_Sort()
TP_Table1='C:/Users/muzamal.pervez/Desktop/Python Scripts/TP1.csv'
TP_Table2=pd.read_csv(TP_Table1)
return TP_Table2.to_dict('records')
if __name__ == '__main__':
app.run_server(debug=False)
I am trying the above approach. Please correct me where i am wrong as the output is error loading dependencies.
BR
Rana
Your callback is wrong.
It should be:
#app.callback(Output('my-table', 'rows'), [Input('graph-update', 'n_intervals')])
def update_table(n, maxrows=4):
# We're now in interval *n*
# Your code
return TP_Table2.to_dict('records')

Bokeh World Map and Coloring Waterbodies

Is there an equivalent way in Bokeh to Basemap's drawmapboundary where you can specify certain colors? See the first example here:
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
# setup Lambert Conformal basemap.
m = Basemap(width=12000000,height=9000000,projection='lcc',
resolution='c',lat_1=45.,lat_2=55,lat_0=50,lon_0=-107.)
# draw coastlines.
m.drawcoastlines()
# draw a boundary around the map, fill the background.
# this background will end up being the ocean color, since
# the continents will be drawn on top.
m.drawmapboundary(fill_color='aqua')
# fill continents, set lake color same as ocean color.
m.fillcontinents(color='coral',lake_color='aqua')
plt.show()
I would like to fill waterbodies (e.g., oceans) with the color "aqua". I'm able to generate a black-and-white world map, but how do I color oceans specifically?
I'm using the JSON file for countries from here, and then loading it with GeoJSONDataSource.
import bokeh.plotting as bkp
import bokeh.models as bkm
filename = "test.html"
tools = "pan,wheel_zoom,box_zoom,reset,previewsave"
with open("./countries.geo.json", "r") as f:
countries = bkm.GeoJSONDataSource(geojson=f.read())
p = bkp.figure(width=1000, height=600, tools=tools, title='World Countries', x_axis_label='Longitude', y_axis_label='Latitude')
p.x_range = bkm.Range1d(start=-180, end=180)
p.y_range = bkm.Range1d(start=-90, end=90)
p.patches("xs", "ys", color="white", line_color="black", source=countries)
bkp.output_file(filename)
bkp.save(p, filename)
Figured out by looking at what drawmapboundary does. Just need to set the background color. :)
import bokeh.plotting as bkp
import bokeh.models as bkm
filename = "test.html"
tools = "pan,wheel_zoom,box_zoom,reset,previewsave"
with open("./countries.geo.json", "r") as f:
countries = bkm.GeoJSONDataSource(geojson=f.read())
p = bkp.figure(width=1000, height=600, tools=tools, title='World Countries', x_axis_label='Longitude', y_axis_label='Latitude')
p.background_fill_color = "aqua"
p.x_range = bkm.Range1d(start=-180, end=180)
p.y_range = bkm.Range1d(start=-90, end=90)
p.patches("xs", "ys", color="white", line_color="black", source=countries)
bkp.output_file(filename)
bkp.save(p, filename)

Resources