I’m afraid that my case is too different from the example in Dynamic Controls and Dynamic Output Components for me to understand an adaptation.
- I think its safe to say that my output container is also my control container
- My generate_output_callback function doesn’t take any arguments
- I am taking a single component property (n_clicks) instead of 2
Below is my attempt of implementation. I noticed that if I change n_clicks in
html.Button('Create Cell', id='cell-geometry-button', n_clicks=0),
then it works for as many plots as n_clicks equals. What am I doing wrong still?
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Output, State, Input
import plotly.graph_objs as go
import numpy as np
import re
app = dash.Dash()
app.config['suppress_callback_exceptions'] = True
#######################################################################################################################
app.layout = html.Div([
html.Div([
dcc.Dropdown(id='material-dropdown'),
html.Button('Add Material', id='add-material-button', n_clicks=0),
html.Div(id='material-options-container'),
html.Button('Create Cell', id='cell-geometry-button', n_clicks=0),
html.Div(id='cell-geometry-config-container'),
html.A(id='click-register'),
]),
])
#######################################################################################################################
# Materials Interface
# Keep track of material names
materials_list = []
# Invoke material options
@app.callback(
Output('material-options-container', 'children'),
[Input('add-material-button', 'n_clicks')],)
def invoke_material_options(n_clicks):
if n_clicks > 0:
options = html.Div([dcc.Input(id='material-name', placeholder='Enter Material Name'),
dcc.Input(id='material-density', placeholder='Enter Material Density', type='number'),
dcc.Input(id='material-temperature', placeholder='Enter Material Temperature', type='number'),
html.Button('Submit Material', id='submit-material-button', n_clicks=0),
html.Br()
])
return options
# Submit material to model
@app.callback(
# Output('material-message-update', 'children'),
Output('material-dropdown', 'options'),
[Input('submit-material-button', 'n_clicks')],
[State('material-name', 'value'),
State('material-density', 'value'),
State('material-temperature', 'value'),
State('material-dropdown', 'options')])
def submit_material(n_clicks, material_name, material_density, material_temperature, material_options):
if n_clicks > 0:
if material_options is not None:
material_options.append({'label': material_name, 'value': len(material_options)+1})
materials_list.append(material_name)
if material_options is None:
material_options = [{'label': material_name, 'value': 0}]
materials_list.append(material_name)
n_clicks = 0
return material_options
#######################################################################################################################
# Geometry Interface
# Initiate cell geometry config with button
@app.callback(
Output('cell-geometry-config-container', 'children'),
[Input('cell-geometry-button', 'n_clicks')],)
def invoke_cell_geometry_options(n_clicks):
# TODO: Below works but must find way to implement fill_region function on arbitrary number of graphs
geometry_ui_list = []
for i in range(n_clicks):
graph_id = 'cell-graph-{}'.format(i)
planes_list_id = 'planes-list-{}'.format(i)
button_id = 'fill-region-button-{}'.format(i)
geometry_ui_list.extend([dcc.Graph(id=graph_id),
dcc.Input(id=planes_list_id, value='.4, .43', placeholder='Enter list of radial planes (comma separated)',
type="text"),
html.Button('Fill Region', id=button_id, n_clicks=0),
html.Br(),
])
options = html.Div(geometry_ui_list)
return options
def generate_output_callbacks():
def fill_region(planes, n_clicks, selected_material, clickData):
planes = [float(plane) for plane in planes.split(',')]
planes.sort()
edge = planes[-1]
x = np.linspace(-edge, edge, 250)
y = np.linspace(-edge, edge, 250)
regions = []
cell_hover = []
# Normal Display
for i in x:
row = []
text_row = []
for j in y:
if np.sqrt(i ** 2 + j ** 2) < planes[0]:
row.append(7) # <- Arbitrary number to adjust color
text_row.append('Region 1')
if np.sqrt(i ** 2 + j ** 2) > planes[-1]:
row.append(5) # <- Arbitrary number to adjust color
text_row.append('Region {}'.format(len(planes) + 1))
for k in range(len(planes) - 1):
if planes[k] < np.sqrt(i ** 2 + j ** 2) < planes[k + 1]:
row.append(k * 3) # <- Arbitrary number to adjust color
text_row.append('Region {}'.format(k + 2))
regions.append(row)
cell_hover.append(text_row)
######################################################
# Initialize region
if clickData is not None:
if 'points' in clickData:
point = clickData['points'][0]
if 'text' in point:
region = int(re.search(r'\d+', point['text']).group())
if 'x' in point:
click_x = point['x']
if 'y' in point:
click_y = point['y']
if n_clicks > 0:
new_hover = []
# Change graph on Click # TODO: Figure out why new text wont show up
if 0 < np.sqrt(click_x ** 2 + click_y ** 2) < planes[0]:
for row_ in cell_hover:
for text in row_:
new_hover.append(
text.replace('Region 1', '{} Region'.format(materials_list[selected_material])))
if np.sqrt(click_x ** 2 + click_y ** 2) > planes[-1]:
for row_ in cell_hover:
for text in row_:
new_hover.append(text.replace('Region {}'.format(len(planes) + 1),
'{} Region'.format(materials_list[selected_material])))
for k in range(len(planes) - 1):
if planes[k] < np.sqrt(click_x ** 2 + click_y ** 2) < planes[k + 1]:
for row_ in cell_hover:
for text in row_:
new_hover.append(text.replace('Region {}'.format(k + 2),
'{} Region'.format(materials_list[selected_material])))
cell_hover = new_hover
n_clicks = 0
######################################################
heatmap = go.Heatmap(z=regions, x=x, y=y, hoverinfo='x+y+text', text=cell_hover, opacity=0.5, showscale=False)
data = [heatmap]
shapes = []
for plane in planes:
shape = {
'type': 'circle',
'x0': -plane,
'y0': -plane,
'x1': plane,
'y1': plane,
'line': {
'width': 4,
},
'opacity': 1
}
shapes.append(shape)
layout = dict(title='Cell Region Depiction',
height=1000,
width=1000,
shapes=shapes)
figure = dict(data=data, layout=layout)
return figure
return fill_region
for val in range(app.layout['cell-geometry-button'].n_clicks):
app.callback(
Output('cell-graph-{}'.format(val), 'figure'),
[Input('planes-list-{}'.format(val), 'value'),
Input('fill-region-button-{}'.format(val), 'n_clicks')],
[State('material-dropdown', 'value'),
State('cell-graph-{}'.format(val), 'clickData')]
)(generate_output_callbacks())
##################################################
if __name__ == '__main__':
app.run_server(debug=True)