plotly world map animation not behaving as expected - python

i am trying to generate an animation using plotly of a world map, with lines drawn between countries representing interactions between countries, and the width of the lines representing the frequency of the interaction. The animation is to show the changes in these interactions over different periods.
i have checked things at each stage of the code, and it all seems to be working as expected up until the actual construction of the animation. the output is an animation, but the behavior is not as expected. I seem to be losing lines (only some of the interactions are showing up in the animation)
I dont have experience with plotly, thanks in advance!
this is a snippet of what my data looks like
From To Frequency Period From_lat_long To_lat_long
11442 VIETNAM SLOVAKIA 3 2019-2022 (15.9266657, 107.9650855) (48.7411522, 19.4528646)
11443 VIETNAM SLOVENIA 1 2019-2022 (15.9266657, 107.9650855) (46.1199444, 14.8153333)
11444 VIETNAM SRI LANKA 1 2019-2022 (15.9266657, 107.9650855) (7.877395849999999, 80.66247852355892)
11445 VIETNAM TANZANIA 1 2019-2022 (15.9266657, 107.9650855) (-6.5247123, 35.7878438)
11446 VIETNAM TUNISIA 1 2019-2022 (15.9266657, 107.9650855) (33.8439408, 9.400138)
I want lines drawn between the From country to the To country, with line widths weighted to the frequency column. And as you see, I have the latitude and longitude coordinates of each country already.
These are the blocks of code, along with what is happening at each step.
# Define the periods
periods = list(data["Period"].unique())
# Create an empty list to store the frames
frames = []
# Loop through each period
for period in periods:
# Create a data frame for the current period
data_period = data.query(f"Period == '{period}'")
# Create a trace for each country
traces = []
for country in set(data_period["From"]).union(set(data_period["To"])):
df_country = data_period.query(f"`From` == '{country}' or `To` == '{country}'")
if not data_period[data_period["From"] == country].empty:
line_width = data_period[data_period["From"] == country][
"Frequency"
].values[0]
elif not data_period[data_period["To"] == country].empty:
line_width = data_period[data_period["To"] == country]["Frequency"].values[
0
]
else:
line_width = 1
traces.append(
go.Scattergeo(
locationmode="country names",
lon=df_country["From_lat_long"].apply(lambda x: x[1]).tolist()
+ [df_country["To_lat_long"].iloc[-1][1]],
lat=df_country["From_lat_long"].apply(lambda x: x[0]).tolist()
+ [df_country["To_lat_long"].iloc[-1][0]],
mode="lines",
line=dict(width=line_width, color="red"),
name=country,
)
)
# Create the figure for this period
fig_period = go.Figure(data=traces)
fig_period.update_layout(
title_text=f"Country interactions for {period}",
geo=dict(
projection=dict(type="natural earth"),
showland=True,
landcolor="rgb(243, 243, 243)",
countrycolor="rgb(204, 204, 204)",
),
)
# Add this figure to the frames list as a frame
frames.append(fig_period)
This is behaving as expected. I can call frame[12] for example and the output is:
output of frame[12]
# Create an empty figure and add the first frame to it
fig = go.Figure(frames[0])
# Update the layout of the figure
fig.update_layout(
title_text="Country interactions",
geo=dict(
projection=dict(type="natural earth"),
showland=True,
landcolor="rgb(243, 243, 243)",
countrycolor="rgb(204, 204, 204)",
),
)
This part seems to work as well, but im not certain if its the correct approach
# create all the new frames
new_frames = [
go.Frame(data=frame["data"], layout=frame["layout"]) for frame in frames[1:]
]
fig.frames = new_frames
this step also works, but possibly the error lies here?
# create the animation
fig.update_layout(
updatemenus=[
dict(
type="buttons",
showactive=True,
buttons=[
dict(
label="Play",
method="animate",
args=[None, {"frame": {"duration": 500, "redraw": True}, "fromcurrent": True, "transition": {"duration": 2000, "easing": "cubic-in-out", "blur": 10}, "traces": [i for i in range(len(frames[21]['data']))]}],
),
dict(
label="Pause",
method="animate",
args=[[None], {"frame": {"duration": 0, "redraw": False}, "mode": "immediate", "transition": {"duration": 0}}],
),
],
)
]
)
this step creates an animation, but i am missing most of the lines.
for example, this is the corresponding image for frame[12] in the animation
corresponding image for frame[12] in the animation
Any help would be appreciated!

Related

Plotly Python update figure with dropMenu

i am currently working with plotly i have a function called plotChart that takes a dataframe as input and plots a candlestick chart. I am trying to figure out a way to pass a list of dataframes to the function plotChart and use a plotly dropdown menu to show the options on the input list by the stock name. The drop down menu will have the list of dataframe and when an option is clicked on it will update the figure in plotly is there away to do this. below is the code i have to plot a single dataframe
def make_multi_plot(df):
fig = make_subplots(rows=2, cols=2,
shared_xaxes=True,
vertical_spacing=0.03,
subplot_titles=('OHLC', 'Volume Profile'),
row_width=[0.2, 0.7])
for s in df.name.unique():
trace1 = go.Candlestick(
x=df.loc[df.name.isin([s])].time,
open=df.loc[df.name.isin([s])].open,
high=df.loc[df.name.isin([s])].high,
low=df.loc[df.name.isin([s])].low,
close=df.loc[df.name.isin([s])].close,
name = s)
fig.append_trace(trace1,1,1)
fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].BbandsMid, mode='lines',name='MidBollinger'),1,1)
fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].BbandsUpp, mode='lines',name='UpperBollinger'),1,1)
fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].BbandsLow, mode='lines',name='LowerBollinger'),1,1)
fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].vwap, mode='lines',name='VWAP'),1,1)
fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].STDEV_1, mode='lines',name='UPPERVWAP'),1,1)
fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].STDEV_N1, mode='lines',name='LOWERVWAP'),1,1)
fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].KcMid, mode='lines',name='KcMid'),1,1)
fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].KcUpper, mode='lines',name='KcUpper'),1,1)
fig.append_trace(go.Scatter(x=df.loc[df.name.isin([s])].time, y=df.loc[df.name.isin([s])].KcLow, mode='lines',name='KcLow'),1,1)
trace2 = go.Bar(
x=df.loc[df.name.isin([s])].time,
y=df.loc[df.name.isin([s])].volume,
name = s)
fig.append_trace(trace2,2,1)
# fig.update_layout(title_text=s)
graph_cnt=len(fig.data)
tr = 11
symbol_cnt =len(df.name.unique())
for g in range(tr, graph_cnt):
fig.update_traces(visible=False, selector=g)
#print(g)
def create_layout_button(k, symbol):
start, end = tr*k, tr*k+2
visibility = [False]*tr*symbol_cnt
visibility[start:end] = [True,True,True,True,True,True,True,True,True,True,True]
return dict(label = symbol,
method = 'restyle',
args = [{'visible': visibility[:-1],
'title': symbol,
'showlegend': False}])
fig.update(layout_xaxis_rangeslider_visible=False)
fig.update_layout(
updatemenus=[go.layout.Updatemenu(
active = 0,
buttons = [create_layout_button(k, s) for k, s in enumerate(df.name.unique())]
)
])
fig.show()
i am trying to add annotations to the figure it will be different for each chart below is how i had it setup for the single chart df['superTrend'] is a Boolean column
for i in range(df.first_valid_index()+1,len(df.index)):
prev = i - 1
if df['superTrend'][i] != df['superTrend'][prev] and not np.isnan(df['superTrend'][i]) :
#print(i,df['inUptrend'][i])
fig.add_annotation(x=df['time'][i], y=df['open'][i],
text= 'Buy' if df['superTrend'][i] else 'Sell',
showarrow=True,
arrowhead=6,
font=dict(
#family="Courier New, monospace",
size=20,
#color="#ffffff"
),)
I adapted an example from the plotly community to your example and created the code. The point of creation is to create the data for each subplot and then switch between them by means of buttons. The sample data is created using representative companies of US stocks. one issue is that the title is set but not displayed. We are currently investigating this issue.
import yfinance as yf
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import pandas as pd
symbols = ['AAPL','GOOG','TSLA']
stocks = pd.DataFrame()
for s in symbols:
data = yf.download(s, start="2021-01-01", end="2021-12-31")
data['mean'] = data['Close'].rolling(20).mean()
data['std'] = data['Close'].rolling(20).std()
data['upperBand'] = data['mean'] + (data['std'] * 2)
data.reset_index(inplace=True)
data['symbol'] = s
stocks = stocks.append(data, ignore_index=True)
def make_multi_plot(df):
fig = make_subplots(rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.03,
subplot_titles=('OHLC', 'Volume Profile'),
row_width=[0.2, 0.7])
for s in df.symbol.unique():
trace1 = go.Candlestick(
x=df.loc[df.symbol.isin([s])].Date,
open=df.loc[df.symbol.isin([s])].Open,
high=df.loc[df.symbol.isin([s])].High,
low=df.loc[df.symbol.isin([s])].Low,
close=df.loc[df.symbol.isin([s])].Close,
name=s)
fig.append_trace(trace1,1,1)
trace2 = go.Scatter(
x=df.loc[df.symbol.isin([s])].Date,
y=df.loc[df.symbol.isin([s])].upperBand,
name=s)
fig.append_trace(trace2,1,1)
trace3 = go.Bar(
x=df.loc[df.symbol.isin([s])].Date,
y=df.loc[df.symbol.isin([s])].Volume,
name=s)
fig.append_trace(trace3,2,1)
# fig.update_layout(title_text=s)
# Calculate the total number of graphs
graph_cnt=len(fig.data)
# Number of Symbols
symbol_cnt =len(df.symbol.unique())
# Number of graphs per symbol
tr = 3
# Hide setting for initial display
for g in range(tr, graph_cnt):
fig.update_traces(visible=False, selector=g)
def create_layout_button(k, symbol):
start, end = tr*k, tr*k+2
visibility = [False]*tr*symbol_cnt
# Number of graphs per symbol, so if you add a graph, add True.
visibility[start:end] = [True,True,True]
return dict(label = symbol,
method = 'restyle',
args = [{'visible': visibility[:-1],
'title': symbol,
'showlegend': True}])
fig.update(layout_xaxis_rangeslider_visible=False)
fig.update_layout(
updatemenus=[go.layout.Updatemenu(
active = 0,
buttons = [create_layout_button(k, s) for k, s in enumerate(df.symbol.unique())]
)
])
fig.show()
return fig.layout
make_multi_plot(stocks)

Adding my Dash Plotly graph as a children property of the callback component stopped it from plotting it correctly

pretty simple problem. I had a graph that would dynamically plot all the values a user enters in the input. They layout had an empty graph on screen(dcc.Graph in layout) even without data entered from the user.
I wanted to change the behavior of my graph work and make it a little more aesthetically pleasing, by having the graph only appear on-screen when a user enters values in the input. To do that I added dcc.Graph in the callback function, using html.Div(id='output-graph') as it’s front-end ID.
After making the changes, my graph stopped working correctly and now only plots the FIRST value a user has entered in the input. It doesn’t matter how many inputs have been entered and acknowledged by the dropdown, my graph won’t plot the remaining ones. Below is my code, I removed everything else apart from the subjects talked about, I didn’t want to clutter the space. If the HTML layout looks wonky that is why.
Could really appreciate some help here. I tried investigating State but I don’t think that would help me here. Curious to know what I did wrong here. Everything else works fine, the flow of data from my dB is not an issue with my other callbacks that use the same input as my graph.
app.layout = html.Div(
dbc.Container(dcc.Dropdown(id="dynamic-dropdown", options=options, multi=True, placeholder="Enter Symbols of Interest"),
html.Div(id='output-graph'),lg=8, md=11, xs=12),justify="center"))
#app.callback(
dash.dependencies.Output('output-graph', 'children'),
[dash.dependencies.Input('dynamic-dropdown', 'value')])
def tickers(symbols):
conn.rollback()
if symbols == None:
raise PreventUpdate
elif symbols == '':
raise PreventUpdate
stock_info = {}
d = {} #dates
p = {} #prices
sym_ids = tuple([id for id in symbols])
stock_info = {}
stock_info = get_dict_resultset("SELECT symbol, date, close FROM security_price WHERE security_price.symbol IN %s;", [sym_ids])
stock_data_by_symbol = defaultdict(list)
for entry in stock_info:
symbol = entry['symbol']
stock_data_by_symbol[symbol].append(entry)
trace = []
for stock in symbols:
d[stock] = [rec['date'] for rec in stock_data_by_symbol[stock]]
p[stock] = [rec['close'] for rec in stock_data_by_symbol[stock]]
trace.append(go.Scatter(x=d[stock],
y=p[stock],
mode='lines',
text = d[stock],
opacity=0.7,
name=stock,
textposition='bottom center'))
traces = [trace]
data = [val for sublist in traces for val in sublist]
figure = {'data': data,
'layout': go.Layout(
colorway=["#5E0DAC", '#FF4F00', '#375CB1', '#FF7400', '#FFF400', '#FF0056'],
paper_bgcolor='rgba(0, 0, 0, 0)',
plot_bgcolor='rgba(0, 0, 0, 0)',
margin={'b': 15},
hovermode='x',
autosize=True,
title={'text': 'Historical Price Graph', 'font': {'color': 'black'}, 'x': 0.5},
),
}
return dcc.Graph(id='output-graph', figure=figure, config={'displayModeBar': False}, animate=True)

Dash Plotly - How to go about solving IndexError: list index out of range', when only the data-source is altered?

I am very new to python coding. With Dash - Plotly, I have plotted sensor data onto a GEO map, below the map is the histogram, which shows the frequency of sensor-observations per hour. There are three drop-down entries to filter the data based on date-picker from a calendar, a sensor picker and an hour picker.
The sample code of Dash-Plotly takes initially its own example CSV file for the image above (location: NY). I took my own CSV and made sure to alter it according to the format of the example CSV's (location: Africa). Which resulted into the following data format (two row sample):
**Date/Time Lat Lon**
2019-03-25 04:00:00 -10,80948998827914 20,19160777427344
2019-03-25 04:05:00 -10,798684405083584 20,16288145431259
My problem: Everything seems to work. Except when I select any date past (say) 7th of the month. Then I get three errors, which for the life of me I don't understand why, because nothing has changed except for the CSV:
EDIT 1:
As an example use-case: in my CSV I have sensor observations for the date '2019-03-23'. So on the webpage when I select the date: March 23, 2019. I get the following errors:
- Callback error updating total-rides.children - IndexError: list index out of range
File "/Frontend/app.py", line 262, in update_total_rides
len(totalList[date_picked.month - 4][date_picked.day - 1])
- Callback error updating histogram.figure - IndexError: list index out of range
File "/Frontend/app.py", line 322, in update_histogram
[xVal, yVal, colorVal] = get_selection(monthPicked,dayPicked, selection)
File "/Users/Mieer/Desktop/DSP_Frontend/app.py", line 231, in **get_selection**
yVal.append(len(totalList[month][day][totalList[month][day].index.hour== i]))
- Callback error updating map-graph.figure - IndexError: list index out of range
File "/Frontend/app.py", line 419, in update_graph
listCoords = getLatLonColor(selectedData, monthPicked,dayPicked)
File "/Users/Mieer/Desktop/DSP_Frontend/app.py", line 382, in **getLatLonColor**
listCoords = totalList[month][day]
My Question: How can one solve the range issue of the list? As for all I know, all that was altered in the code was the datafile which is full of enough data of the right format as I benchmarked the example CSV for which everything worked.
EDIT 2: the code hasn't changed, only the CSV has been replaced with one that considers the exact same format, but with way less records. I have added the callback-graph:
Below my code:
import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import numpy as np
from dash.dependencies import Input, Output
from plotly import graph_objs as go
from plotly.graph_objs import *
from datetime import datetime as dt
app = dash.Dash(
__name__, meta_tags=[{"name": "viewport", "content": "width=device-width"}]
)
server = app.server
# Plotly mapbox public token
mapbox_access_token = "pk.eyJ1IjoicGxvdGx5bWFwYm94IiwiYSI6ImNqdnBvNDMyaTAxYzkzeW5ubWdpZ2VjbmMifQ.TXcBE-xg9BFdV2ocecc_7g"
list_of_fixed_sensors = {
"sensor_1_2": {"lat": -10.736196, "lon": 20.060188},
"sensor_1_3": {"lat": -10.736196, "lon": 20.106700},
"sensor_1_6": {"lat": -10.736196, "lon": 20.246292},
# Initialize data frame
df1 = pd.read_csv(
"/Users/ME/Desktop/Frontend/sensor_points.csv",
dtype=object,
)
df = pd.concat([df1], axis=0)
df["Date/Time"] = pd.to_datetime(df["Date/Time"], format="%Y-%m-%d %H:%M")
df.index = df["Date/Time"]
df.drop("Date/Time", 1, inplace=True)
totalList = []
for month in df.groupby(df.index.month):
dailyList = []
for day in month[1].groupby(month[1].index.day):
dailyList.append(day[1])
totalList.append(dailyList)
totalList = np.array(totalList)
# Layout of Dash App HTML
app.layout = html.Div(
children=[
html.Div(
className="row",
children=[
# Column for user controls
html.Div(
className="four columns div-user-controls",
children=[
html.Img(
className="logo", src=app.get_asset_url("dash-logo-new-.png")
),
html.H2("DASHBOARD - Park Monitoring"),
html.Div(
className="div-for-dropdown",
children=[
dcc.DatePickerSingle(
id="date-picker",
min_date_allowed=dt(2019, 3, 1),
max_date_allowed=dt(2019, 12, 31),
initial_visible_month=dt(2019, 3, 1),
date=dt(2019, 3, 1).date(),
display_format="MMMM DD, YYYY",
style={"border": "0px solid white"}
)
],
),
# Change to side-by-side for mobile layout
html.Div(
className="row",
children=[
html.Div(
className="div-for-dropdown",
children=[
# Dropdown for locations on map
dcc.Dropdown(
id="location-dropdown",
options=[
{"label": i, "value": i}
for i in list_of_fixed_sensors
],
placeholder="Select a location",
)
],
),
html.Div(
className="div-for-dropdown",
children=[
# Dropdown to select times
dcc.Dropdown(
id="bar-selector",
options=[
{
"label": str(n) + ":00",
"value": str(n),
}
for n in range(24)
],
multi=True,
placeholder="Select certain hours",
)
],
),
],
),
html.H1(id="total-rides"),
html.H1(id="total-rides-selection"),
html.H1(id="date-value"),
],
),
# Column for app graphs and plots
html.Div(
className="eight columns div-for-charts bg-grey",
children=[
dcc.Graph(id="map-graph"),
html.Div(
className="text-padding",
children=[
"Select any of the bars on the histogram to section data by time."
],
),
dcc.Graph(id="histogram"),
],
),
],
)
]
)
# Get the amount of rides per hour based on the time selected
# This also higlights the color of the histogram bars based on
# if the hours are selected
def get_selection(month, day, selection):
xVal = []
yVal = []
xSelected = []
colorVal = [
"#F4EC15",
"#DAF017",
"#BBEC19",
"#9DE81B",
"#80E41D",
"#66E01F",
"#4CDC20",
"#34D822",
"#24D249",
"#25D042",
"#26CC58",
"#28C86D",
"#29C481",
"#2AC093",
"#2BBCA4",
"#2BB5B8",
"#2C99B4",
"#2D7EB0",
"#2D65AC",
"#2E4EA4",
"#2E38A4",
"#3B2FA0",
"#4E2F9C",
"#603099",
]
# Put selected times into a list of numbers xSelected
xSelected.extend([int(x) for x in selection])
for i in range(24):
# If bar is selected then color it white
if i in xSelected and len(xSelected) < 24:
colorVal[i] = "#FFFFFF"
xVal.append(i)
# Get the number of rides at a particular time
yVal.append(len(totalList[month][day][totalList[month][day].index.hour == i]))
return [np.array(xVal), np.array(yVal), np.array(colorVal)]
# Selected Data in the Histogram updates the Values in the DatePicker
#app.callback(
Output("bar-selector", "value"),
[Input("histogram", "selectedData"), Input("histogram", "clickData")],
)
def update_bar_selector(value, clickData):
holder = []
if clickData:
holder.append(str(int(clickData["points"][0]["x"])))
if value:
for x in value["points"]:
holder.append(str(int(x["x"])))
return list(set(holder))
# Clear Selected Data if Click Data is used
#app.callback(Output("histogram", "selectedData"), [Input("histogram", "clickData")])
def update_selected_data(clickData):
if clickData:
return {"points": []}
# Update the total number of observations
#app.callback(Output("total-rides", "children"), [Input("date-picker", "date")])
def update_total_rides(datePicked):
date_picked = dt.strptime(datePicked, "%Y-%m-%d")
return "Total number of observations: {:,d}".format(
len(totalList[date_picked.month - 4][date_picked.day - 1])
)
# Update the total number of observations from selected bar in histogram
#app.callback(
[Output("total-rides-selection", "children"), Output("date-value", "children")],
[Input("date-picker", "date"), Input("bar-selector", "value")],
)
def update_total_rides_selection(datePicked, selection):
firstOutput = ""
if selection is not None or len(selection) is not 0:
date_picked = dt.strptime(datePicked, "%Y-%m-%d")
totalInSelection = 0
for x in selection:
totalInSelection += len(
totalList[date_picked.month - 4][date_picked.day - 1][
totalList[date_picked.month - 4][date_picked.day - 1].index.hour
== int(x)
]
)
firstOutput = "Total observations for selected time: {:,d}".format(totalInSelection)
if (
datePicked is None
or selection is None
or len(selection) is 24
or len(selection) is 0
):
return firstOutput, (datePicked, " - showing hour(s): All")
holder = sorted([int(x) for x in selection])
if holder == list(range(min(holder), max(holder) + 1)):
return (
firstOutput,
(
datePicked,
" - showing hour(s): ",
holder[0],
"-",
holder[len(holder) - 1],
),
)
holder_to_string = ", ".join(str(x) for x in holder)
return firstOutput, (datePicked, " - showing hour(s): ", holder_to_string)
# Update Histogram Figure based on Month, Day and Times Chosen
#app.callback(
Output("histogram", "figure"),
[Input("date-picker", "date"), Input("bar-selector", "value")],
)
def update_histogram(datePicked, selection):
date_picked = dt.strptime(datePicked, "%Y-%m-%d")
monthPicked = date_picked.month - 4
dayPicked = date_picked.day - 1
[xVal, yVal, colorVal] = get_selection(monthPicked, dayPicked, selection)
layout = go.Layout(
bargap=0.01,
bargroupgap=0,
barmode="group",
margin=go.layout.Margin(l=10, r=0, t=0, b=50),
showlegend=False,
plot_bgcolor="#323130",
paper_bgcolor="#323130",
dragmode="select",
font=dict(color="white"),
xaxis=dict(
range=[-0.5, 23.5],
showgrid=False,
nticks=25,
fixedrange=True,
ticksuffix=":00",
),
yaxis=dict(
range=[0, max(yVal) + max(yVal) / 4],
showticklabels=False,
showgrid=False,
fixedrange=True,
rangemode="nonnegative",
zeroline=False,
),
annotations=[
dict(
x=xi,
y=yi,
text=str(yi),
xanchor="center",
yanchor="bottom",
showarrow=False,
font=dict(color="white"),
)
for xi, yi in zip(xVal, yVal)
],
)
return go.Figure(
data=[
go.Bar(x=xVal, y=yVal, marker=dict(color=colorVal), hoverinfo="x"),
go.Scatter(
opacity=0,
x=xVal,
y=yVal / 2,
hoverinfo="none",
mode="markers",
marker=dict(color="rgb(66, 134, 244, 0)", symbol="square", size=40),
visible=True,
),
],
layout=layout,
)
# Get the Coordinates of the chosen months, dates and times
def getLatLonColor(selectedData, month, day):
listCoords = totalList[month][day]
# No times selected, output all times for chosen month and date
if selectedData is None or len(selectedData) is 0:
return listCoords
listStr = "listCoords["
for time in selectedData:
if selectedData.index(time) is not len(selectedData) - 1:
listStr += "(totalList[month][day].index.hour==" + str(int(time)) + ") | "
else:
listStr += "(totalList[month][day].index.hour==" + str(int(time)) + ")]"
return eval(listStr)
# Update Map Graph based on date-picker, selected data on histogram and location dropdown
#app.callback(
Output("map-graph", "figure"),
[
Input("date-picker", "date"),
Input("bar-selector", "value"),
Input("location-dropdown", "value"),
],
)
def update_graph(datePicked, selectedData, selectedLocation):
zoom = 10.5
latInitial = -10.736196
lonInitial = 20.060188
bearing = 0
if selectedLocation:
zoom = 13.0
latInitial = list_of_fixed_sensors[selectedLocation]["lat"]
lonInitial = list_of_fixed_sensors[selectedLocation]["lon"]
date_picked = dt.strptime(datePicked, "%Y-%m-%d")
monthPicked = date_picked.month - 4
dayPicked = date_picked.day - 1
listCoords = getLatLonColor(selectedData, monthPicked, dayPicked)
return go.Figure(
data=[
# Data for all rides based on date and time
Scattermapbox(
lat=listCoords["Lat"],
lon=listCoords["Lon"],
mode="markers",
hoverinfo="lat+lon+text",
text=listCoords.index.hour,
marker=dict(
showscale=True,
color=np.append(np.insert(listCoords.index.hour, 0, 0), 23),
opacity=0.5,
size=5,
colorscale=[
[0, "#F4EC15"],
[0.04167, "#DAF017"],
[0.0833, "#BBEC19"],
[0.125, "#9DE81B"],
[0.1667, "#80E41D"],
[0.2083, "#66E01F"],
[0.25, "#4CDC20"],
[0.292, "#34D822"],
[0.333, "#24D249"],
[0.375, "#25D042"],
[0.4167, "#26CC58"],
[0.4583, "#28C86D"],
[0.50, "#29C481"],
[0.54167, "#2AC093"],
[0.5833, "#2BBCA4"],
[1.0, "#613099"],
],
colorbar=dict(
title="Time of<br>Day",
x=0.93,
xpad=0,
nticks=24,
tickfont=dict(color="#d8d8d8"),
titlefont=dict(color="#d8d8d8"),
thicknessmode="pixels",
),
),
),
# Plot of fixed sensors on the map
Scattermapbox(
lat=[list_of_fixed_sensors[i]["lat"] for i in list_of_fixed_sensors],
lon=[list_of_fixed_sensors[i]["lon"] for i in list_of_fixed_sensors],
mode="markers",
marker=dict(size=8, color='white', symbol='square', opacity=0.2),
hoverinfo="text",
text=[i for i in list_of_fixed_sensors],
),
],
layout=Layout(
autosize=True,
margin=go.layout.Margin(l=0, r=35, t=0, b=0),
showlegend=False,
mapbox=dict(
accesstoken=mapbox_access_token,
center=dict(lat=latInitial, lon=lonInitial),
style="dark",
bearing=bearing,
zoom=zoom,
),
updatemenus=[
dict(
buttons=(
[
dict(
args=[
{
"mapbox.zoom": 10.5,
"mapbox.center.lon": "24.060188",
"mapbox.center.lat": "-10.736196",
"mapbox.bearing": 0,
"mapbox.style": "dark",
}
],
label="Reset Zoom",
method="relayout",
)
]
),
direction="left",
pad={"r": 0, "t": 0, "b": 0, "l": 0},
showactive=False,
type="buttons",
x=0.45,
y=0.02,
xanchor="left",
yanchor="bottom",
bgcolor="#323130",
borderwidth=1,
bordercolor="#6d6d6d",
font=dict(color="#FFFFFF"),
)
],
),
)
if __name__ == "__main__":
app.run_server(debug=True)
As the error message suggests, all errors are caused by the index-out-of-range for the array totalList. The reason is quite obvious: totalList contains less elements (smaller shape) than you expect. See this section of your code:
totalList = []
for month in df.groupby(df.index.month):
dailyList = []
for day in month[1].groupby(month[1].index.day):
dailyList.append(day[1])
totalList.append(dailyList)
totalList = np.array(totalList)
It seems that you believe the value of month in the first loop will always iterate through 1, 2, ..., 12 and the day through 1,2,...,31. But those are not guaranteed. The shape of totalList strongly depends on the content of your input data (the CSV file). Let's say the file only contains records for 3 months (e.g. Jan. May and Dec.), then len(totalList)==3. More importantly, totalList[2] will contain the data for May instead of Feb.! So your code totalList[month][...] will give you the wrong data!! This is a more critical issue than those index-out-of-range errors.
You can get the data for a specific month and day by a single line:
df[(df.index.month==month) & (df.index.day==day)] # equivalent to totalList[month][day]
So it is not necessary to make use of the totalList array. I'd suggest you to refactor your code to get rid of this redundant data structure.
SOLUTION
I made the following alterations to the date and day to solve two issues:
List-out-of-Range issue.
The fact that totalList[month][...] outputs wrong data as there was a mismatch between totalList and the date/time of the actual CSV.
I have advanced (refactored) on the following code-snippets to obtain the solution significant to the graph, the histogram and the string that outputs total observations. Basically, the date and day have been corrected. One can compare these new snippet advancements with my original code in the post above.
With these advancements the calendar shows empty results for the
list-items that don't contain observations and shows the actual
correlating results of those list-items that do possess the
observations.
First advancement: added csv to consider the day/times of year.
df2 = pd.read_csv(
"2019daytimes.csv",
dtype=object,
)
df = df.append(df2, ignore_index=True, sort=True)
Second advancement: made the calendar start from first day of observations (static approach not dynamic (I know, later date= should be dynamic and dependent on the first date/time of observations captured in any assigned CSV))
dcc.DatePickerSingle(
id="date-picker",
min_date_allowed=dt(2019, 1, 1),
max_date_allowed=dt(2019, 12, 31),
date=dt(2019, 3, 23).date(),
display_format="MMMM DD, YYYY",
style={"border": "0px solid white"})
Third advancement: added totalList[date_picked.month-1][date_picked.day-1]) - 24
# Update the total number of observations
#app.callback(Output("total-observations", "children"), [Input("date-picker", "date")])
def update_total_rides(datePicked):
date_picked = dt.strptime(datePicked, "%Y-%m-%d")
return "Total number of observations: {:,d}".format(
len(totalList[date_picked.month-1][date_picked.day-1]) - 24
)
Fourth advancement: added [date_picked.month-1][date_picked.day-1] and -1 at end of totalInSelection += len() and if(totalInSelection < 0): totalInSelection = 0
def update_total_rides_selection(datePicked, selection):
firstOutput = ""
if selection is not None or len(selection) is not 0:
date_picked = dt.strptime(datePicked, "%Y-%m-%d")
totalInSelection = 0
for x in selection:
totalInSelection += len(
totalList[date_picked.month-1][date_picked.day-1][
totalList[date_picked.month-1][date_picked.day-1].index.hour
== int(x)
]
)-1
if(totalInSelection < 0):
totalInSelection = 0
firstOutput = "Total observations for selected time: {:,d}".format(totalInSelection)
Fifth advancement: added -1 for month and day of date_picked. And yVal -1
def update_histogram(datePicked, selection):
date_picked = dt.strptime(datePicked, "%Y-%m-%d")
# print(update_histogram)
monthPicked = date_picked.month-1
dayPicked = date_picked.day-1
[xVal, yVal, colorVal] = get_selection(monthPicked, dayPicked, selection)
#print(xVal)
#print(yVal)
yVal = yVal - 1
Sixed advancement: added -1 at month and day of date_picked.
def update_graph(datePicked, selectedData, selectedLocation):
zoom = 10.5
latInitial = -10.8736
lonInitial = 20.1067
bearing = 0
if selectedLocation:
zoom = 13.0
latInitial = list_of_fixed_sensors[selectedLocation]["lat"]
lonInitial = list_of_fixed_sensors[selectedLocation]["lon"]
date_picked = dt.strptime(datePicked, "%Y-%m-%d")
monthPicked = date_picked.month-1
dayPicked = date_picked.day-1
listCoords = getLatLonColor(selectedData, monthPicked, dayPicked)

3D animation of time series in Plotly

I'm prototyping some code to then bring in some more complex time series data (and a lot of it) but can't for the life of me figure out how to get the vector components to animate in the below code. The blue path looks good to start with then disappears on play. And secondly, only the x component displays on play. I've been working mostly off the tutorials on the main plotly site so far, but, as the project builds complexity, my lack of expertise in plotly has let me down. I'm developing in an online Jupyter notebook if someone has any suggestions on how to make my code better. Thanks.
N = 50
vec_x, vec_y, vec_z = [0,0,0]
list_of_lists = []
choice = [-0.2, 0.2]
for i in range(N):
vec_x = vec_x + np.random.choice(choice)
vec_y = vec_y + np.random.choice(choice)
vec_z = vec_z + np.random.choice(choice)
list_of_lists.append([vec_x, vec_y, vec_z])
points = np.array(list_of_lists)
source = points.T
def frameMaker(i):
"""
returns x,y,z dict of currently indexed frame by vector component key
"""
scale = 10
list_of_lists = dict({
"x": [[source[0][i],scale * source[0][i+1]], [source[1][i],source[1][i]], [source[2][i],source[2][i]]],
"y": [[source[0][i],source[0][i]], [source[1][i],scale * source[1][i+1]], [source[2][i], source[2][i]]],
"z": [[source[0][i],source[0][i]], [source[1][i],source[1][i]], [source[2][i], scale * source[2][i+1]]]
})
return list_of_lists
#graphics
plt = go.Figure(
data=[go.Scatter3d(
x=source[0],
y=source[1],
z=source[2],
mode="lines",
line=dict(
color="darkblue",
width=2)),
go.Scatter3d(
x=source[0],
y=source[1],
z=source[2],
mode="lines",
line=dict(
color="darkblue",
width=2))
],
layout =
go.Layout(
title = go.layout.Title(text="Title | Total Frames: "+ str(N)),
scene_aspectmode="cube",
scene = dict(
xaxis = dict(range=[-2,2], nticks=10, autorange=False),
yaxis = dict(range=[-2,2], nticks=10, autorange=False),
zaxis = dict(range=[-2,2], nticks=10, autorange=False)),
updatemenus=[dict(type="buttons",
buttons=[dict(label="Play",
method="animate",
args=[None])])]
),
frames=[go.Frame(
data=[go.Scatter3d(
x = [source[0][k]],
y = [source[1][k]],
z = [source[2][k]],
mode="markers",
marker=dict(color="red",size=10,opacity=0.5)),
go.Scatter3d(
x=frameMaker(k)["x"][0],
y=frameMaker(k)["x"][1],
z=frameMaker(k)["x"][2],
line=dict(color='darkblue',width=2)),
go.Scatter3d(
x=frameMaker(k)["y"][0],
y=frameMaker(k)["y"][1],
z=frameMaker(k)["y"][2],
line=dict(color='#bcbd22',width=2)),
go.Scatter3d(
x=frameMaker(k)["z"][0],
y=frameMaker(k)["z"][1],
z=frameMaker(k)["z"][2],
line=dict(color='#d62728',width=2))],
layout=go.Layout(
title = go.layout.Title(text=str([k+1,list(map(lambda x: round(x,3), source.T[k]))]))
)
) for k in range(N-1)
]
)
plt.show()

Python Plotly: Using dropdown menu to switch between choropleth map and scatterplot

I am new to using plotly and I am attempting to build a dynamic visualisation using python and plotly. I hope to be able to switch between a world choropleth map and a scatter plot using a drop-down menu.
So far I have been able to successfully get a dropdown menu to appear and show the required labels and even show a single plot by removing either the choropleth map or scatter plot trace from the data variable. The problem is that I when I try to have both plots implemented the choropleth map is drawn over the top of the scatterplot regardless of the menu option I choose.
A screenshot of the output.
Areas I Have Looked For A Solution
The plotly reference and looked through the updatemenus and layout sections among many others.
Reviewed the ploty python tutorial page for dropdowns and implementing parts of the suggestion in my code with a focus on the update method.
I have found a StackOverflow page that seemed to be very close to the answer I needed however not quite.
Finally, I also searched the plotly community forum.
The Code
Note I have removed a portion of the code such as imports and data at the beginning.
scatterplot = go.Scatter(
y = df2['Renewable energy consumption (% of total final energy consumption) 2015'],
x = df2['GDP per capita, PPP (constant 2011 international $) 2015'],
mode='markers',
ids=df2['Country Name'],
showlegend = False,
marker = dict(
size = 8,
color = np.random.randn(500),
),
textfont = dict(
size = 14,
color = 'black')
)
choropleth_map = dict(
type = 'choropleth',
locations = df['ISO3166_alpha3'],
z = df['renewables_mtoe'],
text = df['Country'],
colorscale = [[0,"rgb(106, 240, 255)"],[0.10,"rgb(106, 199, 255)"],[0.70,"rgb(50, 100, 255)"],[0.93,"rgb(0, 43, 198)"],\
[0.99999,"rgb(0, 24, 109)"],[1,"rgb(220, 220, 220)"]],
autocolorscale = False,
reversescale = True,
marker = dict(
line = dict (
color = 'rgb(180,180,180)',
width = 0.5
) ),
colorbar = dict(
title = 'mtoe<br>',
tickfont = dict(
size = 16),
titlefont = dict(
size = 16)),
)
data = [choropleth_map, scatterplot]
updatemenus = list([
dict(active=0,
buttons=list([
dict(label = 'choropleth_map',
method = 'update',
args = [{'visible': [True,False]},
{'title': 'The Map'}]),
dict(label = 'scatterplot',
method = 'update',
args = [{'visible': [False,True]},
{'title': 'Scatterplot'}]),
]),
)
])
layout = dict(title='default', showlegend=False,
updatemenus=updatemenus,
geo = dict(showframe = True,
showcoastlines = False,
showland = True,
landcolor = '#dcdcdc',
projection = dict(type = 'natural earth'))
)
fig = dict( data=data, layout=layout )
plotly.offline.iplot(fig, validate=True)
A big thank you in advance to anyone who can help. I have spent days trying to solve this problem, it has even driven me to make my first post on StackOverflow.

Categories

Resources