-
-
Notifications
You must be signed in to change notification settings - Fork 146
partial update of dcc.Graph.figure with LiveGraph composite object #881
Comments
Having given this some though, after a nudge from @alexcjohnson, I'm 99% sure that no new component is needed here, and instead just a (fairly complex) "deep merge" clientside callback and a |
Here's a working version of the approach outlined above: import dash
from jupyter_dash import JupyterDash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import plotly.express as px
import plotly.graph_objects as go
figure = px.scatter(px.data.iris(), x="sepal_length", y="sepal_width")
app = JupyterDash(__name__)
app.layout = html.Div(children = [
dcc.Store(id="figstore", data=figure),
dcc.Store(id="patchstore", data={}),
dcc.Store(id="patchstore2", data={}),
dcc.Dropdown(id="color", value="red",
options=[{"label":x, "value":x} for x in ["red", "blue"]]),
dcc.Dropdown(id="linewidth", value=1,
options=[{"label":x, "value":x} for x in [0,1,2,3]]),
dcc.Graph(id="graph")
])
deep_merge = """
function batchAssign(patches) {
function recursiveAssign(input, patch){
var outputR = Object(input);
for (var key in patch) {
if(outputR[key] && typeof patch[key] == "object") {
outputR[key] = recursiveAssign(outputR[key], patch[key])
}
else {
outputR[key] = patch[key];
}
}
return outputR;
}
return Array.prototype.reduce.call(arguments, recursiveAssign, {});
}
"""
app.clientside_callback(
deep_merge,
Output('graph', 'figure'),
[Input('figstore', 'data'),
Input('patchstore', 'data'),
Input('patchstore2', 'data')]
)
@app.callback(Output('patchstore', 'data'),[Input('color', 'value')])
def cb(color):
return go.Figure(go.Scatter(marker_line_color=color))
@app.callback(Output('patchstore2', 'data'),[Input('linewidth', 'value')])
def cb(width):
return go.Figure(go.Scatter(marker_line_width=width))
app.run_server(mode="jupyterlab") |
@alexcjohnson mentions that actually, we could build a "combiner" component that uses wildcard props like |
(EDITED since I had introduced a bug in a previous attempt) Really cool example! Here it is with a change of layout (annotations don't persist because of #879 but this is a known issue) from jupyter_dash import JupyterDash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import plotly.express as px
import plotly.graph_objects as go
figure = px.scatter(px.data.iris(), x="sepal_length", y="sepal_width")
app = JupyterDash(__name__)
app.layout = html.Div(children = [
dcc.Store(id="figstore", data=figure),
dcc.Store(id="patchstore", data={}),
dcc.Store(id="patchstore2", data={}),
dcc.Store(id="patchstore3", data={}),
dcc.Dropdown(id="color", value="red",
options=[{"label":x, "value":x} for x in ["red", "blue"]]),
dcc.Dropdown(id="linewidth", value=1,
options=[{"label":x, "value":x} for x in [0,1,2,3]]),
dcc.Dropdown(id="dragmode", value='zoom',
options=[{"label":x, "value":x} for x in ['zoom', 'drawrect', 'drawcircle']]),
dcc.Graph(id="graph")
])
deep_merge = """
function batchAssign(patches) {
function recursiveAssign(input, patch){
var outputR = Object(input);
for (var key in patch) {
if(outputR[key] && typeof patch[key] == "object") {
outputR[key] = recursiveAssign(outputR[key], patch[key])
}
else {
outputR[key] = patch[key];
}
}
return outputR;
}
return Array.prototype.reduce.call(arguments, recursiveAssign, {});
}
"""
app.clientside_callback(
deep_merge,
Output('graph', 'figure'),
[Input('figstore', 'data'),
Input('patchstore', 'data'),
Input('patchstore2', 'data'),
Input('patchstore3', 'data')]
)
@app.callback(Output('patchstore', 'data'),[Input('color', 'value')])
def cb(color):
return go.Figure(go.Scatter(marker_line_color=color), layout_template='none')
@app.callback(Output('patchstore2', 'data'),[Input('linewidth', 'value')])
def cb(width):
return go.Figure(go.Scatter(marker_line_width=width))
@app.callback(Output('patchstore3', 'data'),[Input('dragmode', 'value')])
def cb(dragmode):
fig = go.Figure(go.Scatter())
fig.update_layout(dragmode=dragmode, template='none')
return fig
app.run_server(mode="inline") |
Since import dash
from jupyter_dash import JupyterDash
import dash_html_components as html
import dash_core_components as dcc
from dash.dependencies import Input, Output
import plotly.express as px
import plotly.graph_objects as go
figure = px.scatter(px.data.iris(), x="sepal_length", y="sepal_width")
app = JupyterDash(__name__)
app.layout = html.Div(children = [
html.Div(id="patchstore",
**{'data-figure':figure, 'data-patch1':{}, 'data-patch2':{}, 'data-patch3':{}}),
dcc.Dropdown(id="color", value="red",
options=[{"label":x, "value":x} for x in ["red", "blue"]]),
dcc.Dropdown(id="linewidth", value=1,
options=[{"label":x, "value":x} for x in [0,1,2,3]]),
dcc.Dropdown(id="dragmode", value='zoom',
options=[{"label":x, "value":x} for x in ['zoom', 'drawrect', 'drawcircle']]),
dcc.Graph(id="graph")
])
deep_merge = """
function batchAssign(patches) {
function recursiveAssign(input, patch){
var outputR = Object(input);
for (var key in patch) {
if(outputR[key] && typeof patch[key] == "object") {
outputR[key] = recursiveAssign(outputR[key], patch[key])
}
else {
outputR[key] = patch[key];
}
}
return outputR;
}
return Array.prototype.reduce.call(arguments, recursiveAssign, {});
}
"""
app.clientside_callback(
deep_merge,
Output('graph', 'figure'),
[Input('patchstore', 'data-figure'),
Input('patchstore', 'data-patch1'),
Input('patchstore', 'data-patch2'),
Input('patchstore', 'data-patch3')]
)
@app.callback(Output('patchstore', 'data-patch1'),[Input('color', 'value')])
def cb(color):
return go.Figure(go.Scatter(marker_line_color=color), layout_template='none')
@app.callback(Output('patchstore', 'data-patch2'),[Input('linewidth', 'value')])
def cb(width):
return go.Figure(go.Scatter(marker_line_width=width))
@app.callback(Output('patchstore', 'data-patch3'),[Input('dragmode', 'value')])
def cb(dragmode):
fig = go.Figure(go.Scatter())
fig.update_layout(dragmode=dragmode, template='none')
return fig
app.run_server(mode="inline") |
Yes exactly - only of course the combiner component would have the merge logic built in, so instead of the One consequence of this approach, which may or may not be desirable (and is NOT the case with the |
(probably this feature would not be implemented in dcc directly, but I'm posting this feature request here because this is the closest repo)
At the moment, when users want to update a small part of a plotly figure in a
dcc.Graph
in their app, they have two choices:dcc.Graph.figure
as Output, which can result in passing large objects over the network and poor performance for traces with a lot of datadcc.Store
, and update thedcc.Graph
in a clientside callback. This is fast, but this means that users have to write Javascript, which is a shame for a coding pattern which is quite common.We could instead provide an object which would have:
The user-facing API could look like
It would not be possible to target the
dcc.Graph.figure
asOutput
since it would already be used in an internal callback, butfigure
orselectedData
could still be used asInput
orState
. Documentation should be written to illustrate these different cases.Probably this component should be implemented as a standalone package as a first step, but I'm hoping that the dcc devs can chime in here and make suggestions :-).
This development will probably be part of the CZI project on image processing.
The text was updated successfully, but these errors were encountered: