Plotly-Dash: Adding new yaxis per selected df column - python

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.

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))

Output a graph and table from a single dash callback

I would like to generate a graph and table from a dash callback, but the code outputs one or the other.
Below is the final part of the code. The data is filtered by chained callbacks (two drop-downs - LGA and SMA) and a radio button (standard deviation picker).
Is there a simple way to generate the two outputs or do I need to add in another callback and define additional functions?
html.Div(
id='graph-container',
children=[]),
dash_table.DataTable(
id='table-container',
columns = [{"name": i, "id": i} for i in df],
data=df.to_dict('records'),
),
])
Populate the SMA's dropdown with options and values
#app.callback(
Output('SMA-dpdn', 'options'),
Output('SMA-dpdn', 'value'),
Input('LGA-dpdn', 'value'),
)
def set_LGA_options(chosen_LGA):
dff = df[df.LGA==chosen_LGA]
SMAs_of_LGAs = [{'label': c, 'value': c} for c in sorted(dff.SMA.unique())]
values_selected = [x['value'] for x in SMAs_of_LGAs]
return SMAs_of_LGAs, values_selected
#app.callback(
Output('graph-container', 'children'),
Output('table-container', 'data'),
Input('radio_items', 'value'),
Input('SMA-dpdn', 'value'),
Input('LGA-dpdn', 'value'),
prevent_initial_call=True
)
Create graph/table component and populate
def graph(max_deviations, selected_SMA, selected_LGA):
if len(selected_SMA) == 0:
return dash.no_update
else:
dff = df[(df.LGA==selected_LGA) & (df.SMA.isin(selected_SMA))]
data = pd.DataFrame(data=dff)
x = dff.TIME
y = dff.CHANGE
mean = np.mean(y)
standard_deviation = np.std(y)
distance_from_mean = abs(y - mean)
not_outlier = distance_from_mean < max_deviations * standard_deviation
no_outliers = y[not_outlier]
trim_outliers = pd.DataFrame(data=no_outliers)
dfd = pd.merge(trim_outliers, dff, left_index=True, right_index=True)
dfd['CHANGE'] = dfd['CHANGE_x']
fig = px.scatter(dfd, x='TIME', y='CHANGE', color ='SMA', trendline='ols', size='PV', height=500, width=800, hover_name='SMA')
return dfd.to_dict('records')
return dcc.Graph(id='display-map', figure=fig)
if __name__ == '__main__':
app.run_server(debug=False)
Your have correctly defined two Outputs on your callback, but then you are only returning a single value which is incorrect.
The code below is your example stripped down and demonstrates the correct way to return multiple values. I have also switched the no_update logic around to demonstrate a cleaner way to structure the code which reduces the risk of introducing a bug on return:
#app.callback(
Output('graph-container', 'children'),
Output('table-container', 'data'),
Input('radio_items', 'value'),
Input('SMA-dpdn', 'value'),
Input('LGA-dpdn', 'value'),
prevent_initial_call=True
)
def graph(max_deviations, selected_SMA, selected_LGA):
if len(selected_SMA) > 0:
# Do processing to create a dfd record and the figure for the Graph
return dcc.Graph(id='display-map', figure=fig), dfd.to_dict('records')
# No update if length was zero.
return dash.no_update, dash.no_update

Plotly dash creating columns based on user selection

I am working on a selection menu where the user can select the number of experiments they want to compare/see. Depending on this number I would like to adapt the number of columns that appear, being one for each experiment. In the image below an example of the menu with one experiment can be seen. However, I would like these dropdowns to duplicate if the user selects two experiments having them in side-to-side columns.
Here is an example of one column and one experiment:
The code I have so far is the following:
# Load Data
df = px.data.tips()
external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
# Build App
app = JupyterDash(__name__, external_stylesheets=external_stylesheets)
app.layout = html.Div([
# first row - selecting the number of experiments
html.Div([
html.I("Select the number of experiments you want to compare (min - 2 and max - 10):"),
html.Br(),
# input box for the number of experiments
dcc.Input(id="num_exp" , type='number', min = 2, max = 10, placeholder="No of experiments"),
html.Div(id="output")
], style = {'width': '100%'}
),
# second row - where the experiments are organized in columns
html.Div([
html.Label([
html.I("X axis "),
dcc.Dropdown(
id = 'dd_axis',
clearable = False,
placeholder = 'Select property x-axis',
options=[
{'label': i, 'value': i}
for i in properties
],
style={
'width': '100%'
})
]),
html.Label([
html.I("Y Axis "),
dcc.Dropdown(
id = 'dd_yaxis',
clearable = False,
placeholder = 'Select property y-axis',
options=[ ],
disabled = False)
])
], style = {'display': 'inline-block', 'vertical-align': 'top', 'margin-left': '3vw', 'margin-top': '3vw', 'width': '50%'})
])
#app.callback(
Output("output", "children"),
# number of experiments input
Input("num_exp", "value"),
)
def update_output(test_type):
in_testtype = test_type
return u'{} experiments'.format(test_type)
def cb_render(*vals):
return " | ".join((str(val) for val in vals if val))
Can you help me adding more dropdown menus to the side and dividing the second row in columns based in the user selection?
Thank you!
You can do this by using dbc.Col and dbc.Row.
In your layout, have a Div and within it an empty dbc.Row that will be used to populate its children attribute with columns.
Then, in the callback that is triggered by the input box value, have a loop that returns the number of columns you want, with the relevant code for the dbc.Col and what you'd like to display in it.
Here's a worked-up example of your original code:
import dash
from dash import dcc, html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output
app = dash.Dash(external_stylesheets=[dbc.themes.BOOTSTRAP])
properties = ["a", "b", "c"]
app.layout = html.Div([
# first row - selecting the number of experiments
html.Div([
html.I(
"Select the number of experiments you want to compare (min - 2 and max - 10):"),
html.Br(),
# input box for the number of experiments
dcc.Input(id="num_exp", value=2, type='number', min=2,
max=10, placeholder="No of experiments"),
html.Div([
dbc.Row([], id="output")
])
], style={'width': '100%'}
),
])
#app.callback(
Output("output", "children"),
# number of experiments input
Input("num_exp", "value"),
)
def update_output(test_type):
columns_list = []
letters = "xyzabcdefg"
for i in range(test_type):
columns_list.append(
dbc.Col([
html.Label([
html.I(f"{letters[i].upper()} axis "),
dcc.Dropdown(
id=f'{letters[i]}_axis',
clearable=False,
placeholder=f'Select property {letters[i]}-axis',
options=[
{'label': i, 'value': i}
for i in properties
],
style={
'width': '100%'
})
])
])
)
return columns_list
if __name__ == "__main__":
app.run_server(debug=True)

Dash Bootstrap components not changing linked url

I am working on a Dash app and want to include a button/hyperlink to a local html file, however I need to be able to change the html file depending on what search term is input. Below is what I currently have, but I keep getting an error saying the dbc.Button doesn't support 'n-clicks'. I also had an issue with the link immediately being opened when the script was ran. I've never used the Dash Bootstrap components before so I'm not really sure what I need to do to fix this issue.
This is just a snippet of the code so that it wasn't too long
app.layout = html.Div([
html.H1('Gene-NE'),
html.H5('Created by Lauren Kirsch and Dr. Chiquito Crasto'),
html.Label('Search Box'),
dcc.Input(id="search_gene",
type="text",
value='',
placeholder="Type a human gene name",
debounce=True,
minLength=0, maxLength=50,
autoComplete='on',
size='40'),
html.Div([
dcc.Graph(id='mygraph')]),
dcc.RadioItems(
id="vertical_display_toggle",
options=[
{'label': 'Show vertical date bars', 'value': 'show'},
{'label': 'Hide vertical bars', 'value': 'hide'}],
value='hide', # first loading value selected
labelStyle={'display': 'inline-block'}, inputStyle={"margin-left": "8px", "margin-right": "5px"}),
dcc.RadioItems(
id="synonym_display_toggle",
options=[
{'label': 'Show synonyms', 'value': 'show'},
{'label': 'Hide synonyms', 'value': 'hide'}],
value='hide', # first loading value selected
labelStyle={'display': 'inline-block'}, inputStyle={"margin-left": "8px", "margin-right": "5px"}),
html.Div([
dbc.Button("Click Here", id="id-button", className="mr-2"),
html.A(dbc.Nav(dbc.NavItem(dbc.NavLink('Click for PubMedIDs', id='outlink', href='/', target="_blank",
className="nav-link"))))
]),
html.Br(),
html.H6('Texas Tech University Center for Biotechnology and Genomics')])
df = pd.read_csv('list_out.txt', sep='\t', dtype=str)
df = df.transpose().reset_index().rename(columns={'index': 'Date'})
new_header = df.iloc[0]
df = df[1:]
df.columns = new_header
df = df.iloc[0:600]
df = df.set_index('Date')
df = df.iloc[:, ~df.columns.duplicated()]
lookup_df = pd.read_csv('Gene_Lookup.csv', dtype=str)
link = lookup_df.set_index('Approved_Symbol').Linked_Genes.str.split('|').to_dict()
link_date = lookup_df.set_index('Approved_Symbol').Date_Name_Changed.to_dict()
#app.callback(
[Output('mygraph', 'figure'),
Output('outlink', 'children')],
[Input('search_gene', 'value'),
Input('vertical_display_toggle', 'value'),
Input('synonym_display_toggle', 'value'),
Input('id-button', 'n-clicks')])
def update_output(search_gene, vertical_display_user_slct, synonym_display_user_slct, clicks):
if search_gene:
search_gene = search_gene.upper()
syns = link[search_gene]
trace1 = go.Scatter(x=df.index, y=df[search_gene], line_shape='linear', line=dict(color='white'), name=search_gene)
fig = go.Figure()
fig.add_trace(trace1)
if clicks != 0:
return 'f"/assets/{search_gene}.html"'
The main problem is that you've specified n-clicks as input and it needs to be n_clicks instead.
So for clarity, the callback should look more like this:
#app.callback(
[Output("mygraph", "figure"), Output("outlink", "children")],
[
Input("search_gene", "value"),
Input("vertical_display_toggle", "value"),
Input("synonym_display_toggle", "value"),
Input("id-button", "n_clicks"),
],
)
def update_output(
search_gene, vertical_display_user_slct, synonym_display_user_slct, clicks
):
# ...
As far as the link problem goes, I'm not able to reproduce this with what you've shared, but in your callback you have this check:
if clicks != 0:
return 'f"/assets/{search_gene}.html"'
clicks can also be None so make sure this gets handled correctly. Instead you could do something this:
if clicks:
return 'f"/assets/{search_gene}.html"'
This will handle None as well.

Plotting a robot's xy coordinates using matplotlib

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.

Categories