Plotting a robot's xy coordinates using matplotlib - python

I am very new to matplotlib and I am trying to learn on the job here.
I have the XY coordinates of a robot over a period of time and I want to plot these points to visualize the robot movement. My code below (this was possible by other stack overflow experts' generosity) provides a scatter plot of the coordinates. But, I really want just the last 30 entries. Each coordinate is from a frame of a video. The video is 30 frames per second. I want to plot the last second so that I can visualize the motion. each coordinate plotted should vanish after 1/30 of a second.
Say the coords list has 1800 points, representing 60 seconds of data. I want to plot from the beginning of the list, one point per 1/30 of second and when the number of points on the plot reach 30, before 31st index is plotted, the 0th index point is removed from the plot and so one for every successive point from the coords list, till the end. This I think will simulate as if the robot is moving leaving behind a trail of its points for the last 1 second.
I have no idea how to plot a point and let that vanish from the frame after a period of time while new point is plotted. This will simulate a motion.
# read_data() returns a list of tuples of xy coordinates
coords = read_data()
x_list = [int(pt[0]) for pt in coords]
y_list = [int(pt[1]) for pt in coords]
x_min, x_max = min(x_list), max(x_list)
y_min, y_max = min(y_list), max(y_list)
fig=plt.figure()
ax1 = fig.add_subplot(111)
ax1.add_patch( # draw the bounday of the robot movements based on the min and max of coordinates
patches.Rectangle(
(x_min, y_min), # (x,y)
x_max - x_min, # width
y_max - y_min, # height
linewidth=0.1,
fill=False
)
)
ax1.plot(x_list, y_list, 'or', linewidth=0, markersize=0.2) # plot the whole set
# what I really need is just the last 30 entries; each entry comes in 1/30th of a second in actual env

I've come across a similar issue recently, as I was in need of illustrating some motion-tracking data. I assume you solved this in another way (congrats!), but leaving this here for posterity's sake, regardless.
I ended up making it work with Dash (https://plotly.com/dash/).
If you replace the values in the dataframe "df" with your x's and y's, you should have something that does what (I think) you requested. If you replaced the contents of list x and list y with your data, you should be able to get something like what you request.
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
import pandas as pd
import numpy as np
import random
# defining the number of steps
n = 3000
#creating two arrays for containing x and y coordinate -- these are the lists you need to change
#of size equals to the number of size and filled up with 0's
x = 100 + np.zeros(n)
y = 100 + np.zeros(n)
# filling the coordinates with random variables
for i in range(1, n):
val = random.randint(1, 4)
if val == 1:
x[i] = x[i - 1] + 1
y[i] = y[i - 1]
elif val == 2:
x[i] = x[i - 1] - 1
y[i] = y[i - 1]
elif val == 3:
x[i] = x[i - 1]
y[i] = y[i - 1] + 1
else:
x[i] = x[i - 1]
y[i] = y[i - 1] - 1
df = {'X': x, 'Y': y, 'Sample no.': [ind+1 for ind, x in enumerate(x)]}
indx=[ind for ind, x in enumerate(df['Y'])]
time_df = pd.DataFrame({"time": [0], "df_len": [len(df['X'])]})
def df_cond(series, last_n, tail):
if last_n < tail:
s = series[0: last_n]
else:
s = series[(last_n-tail): last_n]
return s
def change(dataframe, size):
try:
dataframe['time'][0] = dataframe['time'][0] + int(size)
return dataframe['time'][0]
except:
return dataframe['time'][0]
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
html.Div([
html.Div([
dcc.Input(id='auto_speed', value=1, type='number'),
], className="two columns"),
html.Div([
dcc.Input(id='trail', value=100, type='number'),
], className="two columns"),
html.Div([
dcc.Input(id='man_start', value=0, type='number'),
], className="two columns"),
html.Div([
dcc.Input(id='man_end', value=0, type='number'),
], className="two columns"),
], className="row"),
html.Div([
html.Div([
html.Div(id='auto_description'),
], className="six columns"),
html.Div([
html.Div(id='man_description')
], className="six columns"),
], className="row"),
dcc.Interval(
id='interval-component',
interval=1*1000, # in milliseconds
n_intervals=0
),
html.Div([
html.Div([
dcc.Graph(id='behaviour', style={'width': 950, 'height': 950}, ),
], className="six columns"),
html.Div([
dcc.Graph(
id='behaviour_full',
style={'width': 950, 'height': 950},
figure={
'data': [
dict(
x=df['X'],
y=df['Y'],
hovertext="xs and ys",
hoverinfo="text",
showlegend = True,
name = "Sample nr: {} - {} of {}".format(0, len(df['X']), len(df['X'])),
mode='markers',
opacity=0.7,
marker={
'size': 5,
'line': {'width': 0.5, 'color': 'white'}
}
)
],
'layout': dict(
xaxis={'type': 'linear', 'title': 'x-positions', 'range': [0,200]},
yaxis={'type': 'linear','title': 'y-positions', 'range': [0,200]},
hovermode='closest',
transition = {'duration': 1}
)
})
], className="six columns"),
], className="row"),
# Hidden div inside the app that stores the intermediate value
html.Div(id='intermediate-value', style={'display': 'none'})
])
#callbacks are activated, when they receive input -- so a callback including an interval-component, will update at the interval-component update-rate
#app.callback(
Output('intermediate-value', 'children'),
[Input('interval-component', 'n_intervals'),
Input('auto_speed', component_property='value'),
Input(component_id='man_start', component_property='value'),
Input(component_id='man_end', component_property='value')]
)
def clean_data(value, size, man_start, man_end):
if man_start == -1:
time_df['time'][0] = man_end
print("zeroed")
return time_df['time'][0]
elif type(man_start) is int and type(man_end) is int and (man_end - man_start) > 0:
return change(time_df, 0)
else:
return change(time_df, size)
#app.callback(
Output(component_id='behaviour', component_property='figure'),
[Input(component_id='auto_speed', component_property='value'),
Input('intermediate-value', 'children'),
Input(component_id='trail', component_property='value'),
Input(component_id='man_start', component_property='value'),
Input(component_id='man_end', component_property='value'),]
)
def update_figure(interval, last_n, tail, man_start, man_end):
if type(man_start) is int and type(man_end) is int and (man_end - man_start) > 0:
return {
'data': [
dict(
x=df['X'][man_start: man_end],
y=df['Y'][man_start: man_end],
hovertext=["X: {}<br>Y: {}<br>Sample: {}".format(df['X'][man_start: man_end][i], df['Y'][man_start: man_end][i], indx[i]+1) for i in indx[man_start: man_end]],
hoverinfo="text",
showlegend = True,
name = "Sample nr: {} - {} of {} <br>{}".format(man_start, man_end, len(df['X']), interval),
mode='markers',
opacity=0.7,
marker={
'size': 5,
'line': {'width': 0.5, 'color': 'white'}
}
)
],
'layout': dict(
xaxis={'type': 'linear', 'title': 'x-positions', 'range': [0,200]},
yaxis={'type': 'linear','title': 'y-positions', 'range': [0,200]},
hovermode='closest',
uirevision= 'dataset',
transition = {'duration': 1}
)
}
else:
if last_n is None:
last_n = 0
return {
'data': [
dict(
x=df_cond(df['X'], last_n, tail),
y=df_cond(df['Y'], last_n, tail),
hovertext=["X: {}<br>Y: {}<br>Sample: {}".format(df['X'][i-1], df['Y'][i-1], df['Sample no.'][i-1]) for i in df_cond(df['Sample no.'], last_n, tail)],
hoverinfo="text",
showlegend = True,
name = "Sample nr: {} - {} of {} <br>{}".format(last_n - len(df_cond(df['X'], last_n, tail))+1, last_n, len(df['X']), interval),
mode='markers',
opacity=0.7,
marker={
'size': 5,
'line': {'width': 0.5, 'color': 'white'}
}
)
],
'layout': dict(
xaxis={'type': 'linear', 'title': 'x-positions', 'range': [0,200]},
yaxis={'type': 'linear','title': 'y-positions', 'range': [0,200]},
hovermode='closest',
uirevision= 'dataset',
transition = {'duration': 1}
)
}
#app.callback(
Output(component_id='auto_description', component_property='children'),
[Input(component_id='auto_speed', component_property='value'),
Input('interval-component', 'n_intervals'),
Input(component_id='man_start', component_property='value'),
Input(component_id='man_end', component_property='value'),
Input(component_id='trail', component_property='value')]
)
def update_output_div(input_value, n_intervals, man_start, man_end, tail):
if man_start == -1:
return ' Auto sample index set to: {}'.format(man_end)
elif type(man_start) is int and type(man_end) is int and (man_end - man_start) > 0:
return ' Manual values being used.'
else:
if tail > time_df['time'][0]:
return ' Automatic slice: {} - {}'.format(0, time_df['time'][0])
else:
return ' Automatic slice: {} - {}'.format(time_df['time'][0]-tail, int(time_df['time'][0]))
#app.callback(
Output(component_id='man_description', component_property='children'),
[Input(component_id='man_start', component_property='value'),
Input(component_id='man_end', component_property='value')]
)
def update_output_div(start_val, end_val):
if start_val is None:
return ' Using auto slice'
elif start_val == -1:
return ' Setting auto sample index to: {}'.format(end_val)
elif end_val - start_val > 0:
return ' Manual slice: {} - {}'.format(start_val, end_val)
else:
return ' Using auto slice'
if __name__ == '__main__':
app.run_server(debug=True)
You can see the illustration by entering the address from the line that says "Running on http://(...)" line in your terminal.
Apropos letting points vanish from the frame -- what is done in this script is basically just a redrawing of a new part of the sample data. I am fairly sure my solution is woefully inefficient. But it does what I need it to do.

Related

Python dash return several values inside for loop

For my dash app, in order to update some graphs dynamically, I have to use a function that I named update_graphs inside a for loop. Some of the graphs contain several traces while some others only have one. The update_graphs function is called inside a callback and returns a dict and an int to update the extendData property of the graph object. However, since I am using a return statement inside a for loop, I only get the first trace.
I am not familiar with the generators and the yield keyword, maybe this is an option. But I haven't been able to make it work.
I have also tried to store the results of the update_graphs inside a list but it is not working.
Any help is appreciated!
Here is the code for the app:
import dash
from dash.dependencies import Output, Input, State, MATCH, ALL
from dash import dcc, html, ctx
import plotly
import plotly.express as px
import random
import plotly.graph_objs as go
import pandas as pd
# Initializing the data with the correct format
init_store = {}
n=3
init_df = pd.DataFrame({'a':pd.Series(dtype='int'), 'b':pd.Series(dtype='int'), 'c':pd.Series(dtype='int'), 'd':pd.Series(dtype='int')}, index=range(50))
init_df['a'] = init_df.index
init_store['0'] = init_df
for i in range(n):
init_df = pd.DataFrame({'a':pd.Series(dtype='int'), 'b':pd.Series(dtype='int')}, index=range(50))
init_df['a'] = init_df.index
init_store[f'{i+1}'] = init_df
# Function to update the dataframes with the new observations
def get_data(json_data):
df = pd.read_json(json_data)
compteur = df['a'][len(df['a'])-1]
if len(df.columns) > 2:
new_row = {'a':compteur + 1, 'b':random.randint(13,26), 'c':random.randint(13,26), 'd':random.randint(13,26)}
else:
new_row = {'a':compteur + 1, 'b':random.randint(13,26)}
df = df.shift(periods=-1)
df.iloc[len(df)-1] = new_row
return(df.to_json())
# Function to update the graphs based on the dataframes
def update_graphs(json_data, column, index=0):
df = pd.read_json(json_data)
nb_obs = df.shape[0]
x_new = df['a'][len(df)-1]
y_new = df[column][nb_obs-1]
return dict(x=[[x_new]], y=[[y_new]]), index
colors = px.colors.qualitative.G10
def generate_graph_containers(index, json_data):
dataframe = pd.read_json(json_data)
X = dataframe['a']
Y = dataframe.loc[:, dataframe.columns != 'a']
graph_id = {'type': 'graph-', 'index': index}
return(
html.Div(
html.Div(
dcc.Graph(
id=graph_id,
style={"height": "8rem"},
config={
"staticPlot": False,
"editable": False,
"displayModeBar": False,
},
figure=go.Figure(
{
"data": [
{
"x": list(X),
"y": list(Y[Y.columns[i]]),
"mode": "lines",
"name": Y.columns[i],
"line": {"color": colors[i+2]},
}
for i in range(len(Y.columns))
],
"layout": {
"uirevision": True,
"margin": dict(l=0, r=0, t=4, b=4, pad=0),
"xaxis": dict(
showline=False,
showgrid=False,
zeroline=False,
showticklabels=False,
),
"yaxis": dict(
showline=False,
showgrid=False,
zeroline=False,
showticklabels=False,
),
"paper_bgcolor": "rgba(0,0,0,0)",
"plot_bgcolor": "rgba(0,0,0,0)",
}
}
)
)
)
)
)
app = dash.Dash(__name__)
store = [dcc.Store(id={'type':'store-', 'index':i}, data=init_store[str(i)].to_json()) for i in range(n)]
def make_layout():
return(
html.Div(
[
html.Div(
store
),
dcc.Interval(
id = 'interval',
interval = 1000,
n_intervals = 0
),
html.Div(
[
generate_graph_containers(str(i), store[i].data) for i in range(n)
]
)
]
)
)
app.layout = make_layout
#app.callback(
Output(component_id={'type':'store-', 'index':MATCH}, component_property='data'),
[
Input('interval', 'n_intervals'),
State(component_id={'type':'store-', 'index':MATCH}, component_property='data')
]
)
def update_data(time, data):
return(get_data(data))
#app.callback(
Output(component_id={'type':'graph-', 'index':MATCH}, component_property='extendData'),
Input(component_id={'type':'store-', 'index':MATCH}, component_property="data")
)
def update_graphs_callback(data):
triggered_id = ctx.triggered_id
print(triggered_id['index'])
columns = ['b', 'c', 'd']
if triggered_id['index'] == 0:
for i in range(len(columns)):
return(update_graphs(data, columns[i], i))
else:
return(update_graphs(data, 'b'))
if __name__ == '__main__':
app.run_server(debug=True)
I figured it out. The trick is in the format expected to update the extendData property of a figure. When trying to update several traces, the format should be a dictionary with a key for the x values and one for the y values. The values associated should be an array for each key, containing an array per trace. Don't forget to add the trace indices after the dictionary. So for example, in the case of 3 distinct traces, the function should return something like:
dict(x=[[x_0], [x_1], [x_2]], y=[[y_0], [y_1], [y_2]]), [0, 1, 2]
Therefore the update_graphs function should be:
def update_graphs(json_data):
df = pd.read_json(json_data)
nb_obs = df.shape[0]
x_new = []
y_new = []
trace_index = []
for i in range(len(df.columns)-1):
x_new.append([df['a'][len(df)-1]])
y_new.append([df[df.columns[i+1]][nb_obs-1]])
trace_index.append(i)
return(dict(x=x_new, y=y_new), trace_index)
And the callback to update the graphs should be changed to:
#app.callback(
Output(component_id={'type':'graph-', 'index':MATCH}, component_property='extendData'),
Input(component_id={'type':'store-', 'index':MATCH}, component_property="data")
)
def update_graphs_callback(data):
return(update_graphs(data))

Trying to get a value from a df to use as a value for axis range in an indicator axis

I have been trying to put together a Plotly Dash app that will pull up our clients current hours and historical hours with the current hours being represented as a gauge of how many have been used versus how many are allowed, which varies from client to client so there is no set value I can input as an overall value. So, value = hours spent and range is [0, hours allowed]
I have tried using .iloc, and .values but neither have worked to single out the integer as an independent variable to be used. However, I also have a feeling that I am screwing something up in general with the dash, so if anyone can help me unscrew this so I can get it presented by Friday would be lovely.
Edit
#mozway, sorry about that (also, apparently enter sends). The csv looks like this:
Client | Hours Spent | Hours Allowed | Last Updated
XXXX | 30.81 | 60 | 2021-09-07
And so on. As for pinpoint, it gives me a error at the Indicator figure
elif client != "All":
dff = dfhours.query('Client == "{}"'.format(client))
ha = dff.values[0][5]
fig = go.Figure(go.Indicator(
domain = {'x': [0, 1], 'y': [0, 1]},
value = dff['Hours Spent'],
mode = "gauge+number",
gauge = {'axis': {'range':[None, ha]}}))
ValueError:
Invalid value of type 'pandas.core.series.Series' received
for the 'value' property of indicator
Received value: 0 30.81
Name: Hours Spent, dtype: float64
The 'value' property is a number and may be specified as:
- An int or float
It is supposed to use the values from Hours Spent as the Value for the gauge, and the Hours Allowed as the end of the gauge.
End Edit
app = dash.Dash(__name__)
dfhours = pd.read_csv("hothours9-7.csv")
dfhours['Last Updated'] = pd.to_datetime(dfhours['Last Updated'])
dfclients = pd.read_csv("hotclients9-7.csv")
clients = clientlist['Client'].unique()
app.layout = html.Div(children=[
html.H1(
children='Hello!',
style={
'textAlign': 'center'
}
),
html.Br(),
html.Div([
html.Label('Clients'),
dcc.Dropdown(
id='clients-list',
options=[{'label': i, 'value': i} for i in clients],
value='All',
style = {'width': "80%"}
),
dcc.Dropdown(
id='info-drop',
options = [{'label': i, 'value': i} for i in ['Historical Hours', 'Current Hours']],
value = 'Current Hours',
)
]),
html.Br(),
dcc.Graph(id='info-graph')
])
#-------------------------------------------
#app.callback(
Output('info-graph','figure'),
[Input('clients-list','value'),
Input('info-drop','value')])
def update_graph(client,info):
if info == "Current Hours":
if client == "All":
fig = px.bar(dfhours, x="Client", y="Hours Spent")
elif client != "All":
dff = dfhours.query('Client == "{}"'.format(client))
ha = dff.values[0][5]
fig = go.Figure(go.Indicator(
domain = {'x': [0, 1], 'y': [0, 1]},
value = dff['Hours Spent'],
mode = "gauge+number",
gauge = {'axis': {'range':[None, ha]}}))
elif info == 'Historical Hours':
if client == "All":
dcc.Checklist(
options = [{"label": x, "value": x} for x in dfclients['Client']]),
fig = px.line(dfclients,x="Last Updated",y="Hours Spent",color="Client")
elif client != "All":
dff = dfclients.query('Client == "{}"'.format(client)),
fig = px.line(dff, x="Last Updated",y="Hours Spent")
return fig
if __name__=='__main__':
app.run_server(debug=False)
have simulated your data...
simple case of reset_index() after filtering dataframe then allows you to always access row as index 0 (assumes one row per client)
have used dash 1.0.0 hence html and dcc packages are not imported but referenced
you construct checklist in callback that is not used anywhere...
from jupyter_dash import JupyterDash
import dash
from dash.dependencies import Input, Output, State
import pandas as pd
import numpy as np
import plotly.express as px
import plotly.graph_objects as go
# app = dash.Dash(__name__)
app = JupyterDash(__name__)
# dfhours = pd.read_csv("hothours9-7.csv")
# dfhours['Last Updated'] = pd.to_datetime(dfhours['Last Updated'])
# dfclients = pd.read_csv("hotclients9-7.csv")
# simulate data...
h = np.random.uniform(10, 40, 4)
dfhours = pd.DataFrame(
{
"Client": list("ABCD"),
"Hours Spent": h,
"Hours Allowed": h * np.random.uniform(1.5, 2, 4),
}
)
clientlist = pd.DataFrame({"Client": list("ABCD")})
clients = clientlist["Client"].unique()
dfclients = pd.DataFrame(
{
"Last Updated": pd.date_range("1-May-2021", periods=60),
"Client": np.random.choice(list("ABCD"), 60),
"Hours Spent": np.random.uniform(10, 40, 60),
}
)
app.layout = dash.html.Div(
children=[
dash.html.H1(children="Hello!", style={"textAlign": "center"}),
dash.html.Br(),
dash.html.Div(
[
dash.html.Label("Clients"),
dash.dcc.Dropdown(
id="clients-list",
options=[{"label": i, "value": i} for i in clients],
value="All",
style={"width": "80%"},
),
dash.dcc.Dropdown(
id="info-drop",
options=[
{"label": i, "value": i}
for i in ["Historical Hours", "Current Hours"]
],
value="Current Hours",
),
]
),
dash.html.Br(),
dash.dcc.Graph(id="info-graph"),
]
)
# -------------------------------------------
#app.callback(
Output("info-graph", "figure"),
[Input("clients-list", "value"), Input("info-drop", "value")],
)
def update_graph(client, info):
if info == "Current Hours":
if client == "All":
fig = px.bar(dfhours, x="Client", y="Hours Spent")
elif client != "All":
dff = dfhours.loc[dfhours["Client"].eq(client)].reset_index(drop=True)
fig = go.Figure(
go.Indicator(
domain={"x": [0, 1], "y": [0, 1]},
value=dff.loc[0, "Hours Spent"],
mode="gauge+number",
gauge={"axis": {"range": [0, dff.loc[0, "Hours Allowed"]]}},
)
)
elif info == "Historical Hours":
if client == "All":
# this is spurious !!!
dash.dcc.Checklist(
options=[{"label": x, "value": x} for x in dfclients["Client"]]
),
fig = px.line(dfclients, x="Last Updated", y="Hours Spent", color="Client")
elif client != "All":
dff = dfclients.query('Client == "{}"'.format(client))
fig = px.line(dff, x="Last Updated", y="Hours Spent")
return fig
if __name__ == "__main__":
# app.run_server(debug=False)
app.run_server(mode="inline")

How to make callback for DatePickerRange and Graph in Dash

I just started to use Plotly and Dash. I try to connect my interactive graph with calendar (DatePickerRange). Both off them display correctly, but not together. My figure doesn’t see the start and end dates.
My variable date is “session_date”. I don’t know how to use the start and end variable with session_date. The range from session_date.
I have started to put start_date and end_date to call-back, byt that didn't display correctly.
Could someone help me?
With regards
Dorota
# -----------------------------------
# Connect to database OR data source here
# -----------------------------------
...
# -----------------------------------
# Retrieve data from database
# -----------------------------------
curs = db.cursor()
testy = """SELECT o.ticker, o.issuer, o.session_date, o.open, o.close, o.min
FROM olhc AS o;"""
curs.execute(testy)
data = []
for x in curs:
data.append(x)
df = pd.DataFrame(data, columns=['ticker', 'issuer', 'session_date', 'open', 'close', 'min'])
# convert the 'Session_date' column to datetime format
df['session_date']= pd.to_datetime(df['session_date'])
# -----------------------------------
# Initialize the app
# -----------------------------------
# This application is using a custom
# CSS stylesheet to modify the default
# styles of the elements.
# -----------------------------------
external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
colors = dict(background='#3333',
text='#21617A',
font='#21617A')
# -----------------------------------
# Define app layout
# -----------------------------------
app.layout = html.Div(children=[
html.H1(children='Close & Open prices for company',
style={'textAlign': 'center', 'color': colors['text']}
),
html.Div(children=[
html.Div(style={
'textAlign': 'center', 'color': colors['font']
},
children='''Financial Dashboard for FellowshipPL
'''),
dcc.DatePickerRange(
id='my-date-picker-range',
calendar_orientation='horizontal',
day_size=30,
first_day_of_week=1, # 0 Sunday
clearable=False,
with_portal=False, # True on the page
min_date_allowed=date(2010, 1, 1),
max_date_allowed=date(2021, 12, 31),
initial_visible_month=date(2020, 1, 1),
start_date=date(2020, 1, 1),
end_date=date(2021, 12, 31),
display_format='MMM Do, YYYY', # lots possibilities
updatemode='singledate'
),
html.Div(id='output-container-date-picker-range'),
# -----------------------------------
# Define Dropdown
# -----------------------------------
dcc.Dropdown(style={
'textAlign': 'left',
'color': colors['text']
},
id='issuer_selection',
options=[
{'label': i, 'value': i} for i in df.issuer.unique()
], multi=False,
placeholder='Filter by name of company ...'),
html.H3(id='text'),
dcc.Graph(id='indicators')])
])
# -----------------------------------
# Define first callback
# -----------------------------------
#app.callback(
Output('output-container-date-picker-range', 'children'),
[Input('my-date-picker-range', 'start_date'),
Input('my-date-picker-range', 'end_date')])
def update_output(start_date, end_date):
string_prefix = 'You have selected: '
if start_date is not None:
start_date_object = date.fromisoformat(start_date)
start_date_string = start_date_object.strftime('%B %d, %Y')
string_prefix = string_prefix + 'Start Date: ' + start_date_string + ' | '
if end_date is not None:
end_date_object = date.fromisoformat(end_date)
end_date_string = end_date_object.strftime('%B %d, %Y')
string_prefix = string_prefix + 'End Date: ' + end_date_string
if len(string_prefix) == len('You have selected: '):
return 'Select a date to see it displayed here'
else:
return string_prefix
# #app.callback(
# Output('indicators', 'figure'),
# [Input('issuer_selection', 'value'),
# Input('my-date-picker-range', 'start_date'),
# Input('my-date-picker-range', 'end_date')])
#app.callback(
Output('indicators', 'figure'),
[Input('issuer_selection', 'value')])
def retrieve_plots(issuer):
filtered_df = df[df['issuer'] == issuer]
# dff = df.loc[start_date:end_date]
# Creating trace1
trace1 = go.Scatter(x=(filtered_df['session_date']),
y=filtered_df['close'],
mode="markers",
name="Close price",
marker=dict(color='#21617A', size=4),
text=filtered_df['session_date'])
# Creating trace2
trace2 = go.Scatter(x=(filtered_df['session_date']),
y=filtered_df['open'],
mode="markers",
name="Open price",
marker=dict(color='#C22E4C', size=3),
text=filtered_df.session_date)
# Creating trace3
trace3 = go.Scatter(x=(filtered_df['session_date']),
y=filtered_df['min'],
mode="markers",
name="Min price",
marker=dict(color='#7FD13A', size=2),
text=filtered_df.session_date)
data = [trace1, trace2, trace3]
layout = dict(yaxis=dict(title='Prices', ticklen=5, zeroline=False),
xaxis=dict(title='Date', ticklen=5, zeroline=False),
hovermode="x unified",
style={'textAlign': 'center',
'color': colors['text']
},
)
datapoints = {'data': data, 'layout': layout}
return datapoints
# -----------------------------------
# Run the app
# -----------------------------------
if __name__ == '__main__':
app.run_server(debug=True)

Plotly-Dash: Adding new yaxis per selected df column

I have a basic dash app that graphs some data from a dictionary of dataframes. The first dropdown selects the df, while the second selects the columns of the df to be plotted.
This works well, but I can't seem to add a new yaxis for each of the plotted columns. I have a large number of columns in each df and they change depending on the df that is selected.
First, I tried to change the updateGraph callback to include yaxis=i after defining x, y & name. Looking at the documentation, it seems that I can define the yaxis in go.Scatter but that I would need to set them as 'y2', 'y3, 'y4' etc. I've also tried to update the layout via go.Figure.add_trace in this way but neither has worked. The code is below, where dict_main is a dictionary of dataframes of various sizes.
All help is appreciated!
data = list(dict_main.keys())
channels = dict_main[data[0]]
app.layout = html.Div(
[
html.Div([
dcc.Dropdown(
id='data-dropdown',
options=[{'label': speed, 'value': speed} for speed in data],
value=list(dict_main.keys())[0],
searchable=False
),
], style={'width': '49%', 'display': 'inline-block'}),
html.Div([
dcc.Dropdown(
id='channel-dropdown',
multi=True
),
], style={'width': '49%', 'display': 'inline-block'}
),
html.Div([
dcc.Graph(
id='Main-Graph',
),
], style={'width': '98%', 'display': 'inline-block'}
)
]
)
#app.callback(
Output('channel-dropdown', 'options'),
[Input('data-dropdown', 'value')])
def update_date_dropdown(speed):
return [{'label': i, 'value': i} for i in dict_main[speed]]
#app.callback(
Output('Main-Graph', 'figure'),
[Input('channel-dropdown', 'value')],
[State('data-dropdown', 'value')])
def updateGraph(channels, speed):
if channels:
return go.Figure(data=[go.Scatter(x=dict_main[speed].index, y=dict_main[speed][i], name=i, yaxis='y2') for i in channels])
else:
return go.Figure(data=[])
if __name__ == '__main__':
app.run_server()
UPDATE! This works, although some small changes to color and position are still needed - Thanks to #Philipp for all the help;
#app.callback(
Output('Main-Graph', 'figure'),
[Input('channel-dropdown', 'value')],
[State('rpm-dropdown', 'value')])
def updateGraph(channels, test):
if channels:
j=1
my_layout = {}
my_axis = list("")
for index, column in enumerate(list(channels)):
my_layout['yaxis' + str(j) if j > 1 else 'yaxis'] = {}
my_layout['yaxis' + str(j) if j > 1 else 'yaxis']['title'] = column
my_layout['yaxis' + str(j) if j > 1 else 'yaxis']['overlaying'] = 'y' if j > 1 else 'free'
my_layout['yaxis' + str(j) if j > 1 else 'yaxis']['anchor'] = 'free'
my_layout['yaxis' + str(j) if j > 1 else 'yaxis']['side'] = 'left'
my_axis.append('y' + str(j) if j > 1 else 'y')
j+=1
return go.Figure(data=[go.Scatter(x=dict_main[test].index, y=dict_main[test][column], name=column, yaxis=my_axis[index]) for index, column in enumerate(channels)],layout=my_layout)
else:
return go.Figure(data=[])
You have to define every y-axis in the layout property of your graph (right now you're only setting the data property). See this example.
If you don't want to draw all y-axes (if your df has many columns) you have to set some of them invisible via setting variables like [overlaying, ticks, showticklabels, showgrid, zeroline] (you can find info about them here) but they still have to be defined accordingly in the layout so you can refer to them in the scatter function.

Plotly Dash with an opportunity to delete and interpolate outliers

I am building a dashboard for plotting graphs with an opportunity to exclude outliers by clicking on points on the graph and change the data with interpolated values.
The main idea of this dashboard is to make data preparation faster and easier for people who don’t use Python. Also this dash will be used for simple data visualisation (sort of power BI handmade). And after all iterations a new file with clear data will be written as .csv without outliers.
Working on this i ran into 2 problems:
How to connect imported by dashboard interface data with plot layout.
How to choose points on the graph or choose time period in datePickerRange, delete values and interpolate (scipy.interpolate.interp1d) missing(deleted) values or change them with moving mean (pd.rolling_mean()).
Also I found out that pandas interpolations yields kind of same results, so it will be fine to use it.
There is code block for data parsing:
def parse_contents(contents, filename, date):
content_type, content_string = contents.split(',')
decoded = base64.b64decode(content_string)
try:
if 'csv' in filename:
# Assume that the user uploaded a CSV file
df = pd.read_csv(io.StringIO(decoded.decode('cp1251')), sep = ';' )
elif 'tsv' in filename:
# Assume that the user uploaded a TSV file
df = pd.read_csv(io.StringIO(decoded.decode('utf-8')), sep = '\t')
elif 'xls' in filename:
# Assume that the user uploaded an excel file
df = pd.read_excel(io.BytesIO(decoded))
elif 'xlsx' in filename:
# Assume that the user uploaded a new excel file
df = pd.read_excel(io.BytesIO(decoded))
except Exception as e:
print(e)
return html.Div([
'There was an error processing this file.'
])
return html.Div([
html.H5(filename),
html.H6(datetime.datetime.fromtimestamp(date)),
dash_table.DataTable(
data = df.to_dict('rows'),
columns = [{'name': i, 'id': i} for i in df.columns]),
html.Hr(), # horizontal line
# For debugging, display the raw contents provided by the web browser
html.Div('Raw Content'),
html.Pre(contents[0:10] + '...', style = {
'whiteSpace': 'pre-wrap',
'wordBreak': 'break-all'
})
])
and callback function for input box:
#dashboard.callback(
Output('output-data-upload', 'children'),
[Input('upload-data', 'contents')],
[State('upload-data', 'filename'),
State('upload-data', 'last_modified')])
def update_output(list_of_contents, list_of_names, list_of_dates):
if list_of_contents is not None:
children = [parse_contents(c, n, d) for c, n, d in zip(list_of_contents, list_of_names, list_of_dates)]
return children
This part of code literally was taken from official documentation.
It is fascinating that I can look on uploaded data, but I would like to use column names and date from these columns for plotting the same way I do it in Pandas.
To choose column name I created two dropdown components:
#Create dropdown for X-axis
html.Div([
dcc.Dropdown(
id = 'xaxis-column',
options = [{'label': i, 'value': i} for i in df.columns],
value = 'Xdate')],
style = {'width': '48%', 'display': 'inline-block'}),
#Create dropdown for Y-axis
html.Div([
dcc.Dropdown(
id = 'yaxis-column',
options = [{'label': i, 'value': i} for i in df.columns],
value = 'Yval')],
style = {'width': '48%', 'float': 'right', 'display': 'inline-block'})
And the part of code for graph:
dcc.Graph(id = 'graph')
#dashboard.callback(
Output('graph', 'figure'),
[Input('xaxis-column', 'value'),
Input('yaxis-column', 'value'),
Input('xaxis-type', 'value'),
Input('yaxis-type', 'value'),
Input('XYeardate--slider', 'value')])
def update_graph(xaxis_column_name, yaxis_column_name,
xaxis_type, yaxis_type, Year_value):
dff = df[df['XYeardate'] == Year_value]
return {
'data': [go.Scatter(
x = dff[dff['Xval'] == xaxis_column_name]['Xdate'],
y = dff[dff['Xval'] == yaxis_column_name]['Yval'],
text = dff[dff['Xval'] == yaxis_column_name]['ID'],
mode = 'markers',
marker = {
'size': 10, #was 'size': 15
'opacity': 0.5,
'line': {'width': 0.5, 'color': 'white'}})],
'layout': go.Layout(
xaxis = {
'title': xaxis_column_name,
'type': 'linear' if xaxis_type == 'Linear'},
yaxis = {
'title': yaxis_column_name,
'type': 'linear' if yaxis_type == 'Linear'},
margin = {'l': 40, 'b': 40, 't': 10, 'r': 0},
hovermode = 'closest')}
I can add other parts of code in comments if needed.
Any comments would be appreciated!

Categories