Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Enhancement: add property Graph.extendData to support Plotly.extendTraces #461

Merged
merged 22 commits into from
Mar 25, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
add6589
merge diff from dash-extendable-graph 0.1.0
bcliang Feb 18, 2019
27da421
dcc doesn't import pytest_dash (module wait_for not found). Call defi…
bcliang Feb 18, 2019
3820f07
forgot to port some code over, fix tests
bcliang Feb 18, 2019
7484266
ran prettier
bcliang Feb 18, 2019
ad0d68e
Merge branch 'master' of github.com:bcliang/dash-core-components into…
bcliang Mar 3, 2019
2352e62
Merge branch 'master' of github.com:bcliang/dash-core-components into…
bcliang Mar 10, 2019
1741e35
use extendTraces api
bcliang Mar 11, 2019
461dbcb
update integration test for extendTraces api
bcliang Mar 12, 2019
982a9f1
Merge branch 'master' of github.com:plotly/dash-core-components into …
bcliang Mar 12, 2019
96f6bc5
respect ignored pep8 rules
bcliang Mar 12, 2019
2999f8d
broken test.test_integration.Tests.test_graph_extend_trace
bcliang Mar 14, 2019
ecb6c6e
fix tests, npm build
bcliang Mar 14, 2019
d6cb9d3
fix broken test_confirm_as_children
bcliang Mar 14, 2019
d39eb8e
npm run format
bcliang Mar 14, 2019
f2410a2
sync with master (support relayout data)
bcliang Mar 19, 2019
b75cadd
fix typo, run build
bcliang Mar 19, 2019
cd7c409
fix conflicts with master, rebuild with new webpack version
bcliang Mar 23, 2019
1e17e30
add maxPoints, make traceIndices optional
bcliang Mar 25, 2019
b0a0469
fix extendData object equality, allow error on failure
bcliang Mar 25, 2019
f196b4b
sync with plotly/master 0.45.0
bcliang Mar 25, 2019
022c523
allow repeated extends, handle null traceIndices
bcliang Mar 25, 2019
0c9a480
sync with plotly/master
bcliang Mar 25, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions dash_core_components/Graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ class Graph(Component):
when the user zooms or pans on the plot or other
layout-level edits. Has the form `{<attr string>: <value>}`
describing the changes made. Read-only.
- extendData (dict; optional): Data that should be appended to existing traces. Has the form
`[updateData, traceIndices, maxPoints]`, where `updateData` is an object
containing the data to extend, `traceIndices` (optional) is an array of
trace indices that should be extended, and `maxPoints` (optional) is
either an integer defining the maximum number of points allowed or an
object with key:value pairs matching `updateData`
Reference the Plotly.extendTraces API for full usage:
https://plot.ly/javascript/plotlyjs-function-reference/#plotlyextendtraces
- restyleData (list; optional): Data from latest restyle event which occurs
when the user toggles a legend item, changes
parcoords selections, or other trace-level edits.
Expand Down Expand Up @@ -102,12 +110,12 @@ class Graph(Component):
- prop_name (string; optional): Holds which property is loading
- component_name (string; optional): Holds the name of the component that is loading"""
@_explicitize_args
def __init__(self, id=Component.UNDEFINED, clickData=Component.UNDEFINED, clickAnnotationData=Component.UNDEFINED, hoverData=Component.UNDEFINED, clear_on_unhover=Component.UNDEFINED, selectedData=Component.UNDEFINED, relayoutData=Component.UNDEFINED, restyleData=Component.UNDEFINED, figure=Component.UNDEFINED, style=Component.UNDEFINED, className=Component.UNDEFINED, animate=Component.UNDEFINED, animation_options=Component.UNDEFINED, config=Component.UNDEFINED, loading_state=Component.UNDEFINED, **kwargs):
self._prop_names = ['id', 'clickData', 'clickAnnotationData', 'hoverData', 'clear_on_unhover', 'selectedData', 'relayoutData', 'restyleData', 'figure', 'style', 'className', 'animate', 'animation_options', 'config', 'loading_state']
def __init__(self, id=Component.UNDEFINED, clickData=Component.UNDEFINED, clickAnnotationData=Component.UNDEFINED, hoverData=Component.UNDEFINED, clear_on_unhover=Component.UNDEFINED, selectedData=Component.UNDEFINED, relayoutData=Component.UNDEFINED, extendData=Component.UNDEFINED, restyleData=Component.UNDEFINED, figure=Component.UNDEFINED, style=Component.UNDEFINED, className=Component.UNDEFINED, animate=Component.UNDEFINED, animation_options=Component.UNDEFINED, config=Component.UNDEFINED, loading_state=Component.UNDEFINED, **kwargs):
self._prop_names = ['id', 'clickData', 'clickAnnotationData', 'hoverData', 'clear_on_unhover', 'selectedData', 'relayoutData', 'extendData', 'restyleData', 'figure', 'style', 'className', 'animate', 'animation_options', 'config', 'loading_state']
self._type = 'Graph'
self._namespace = 'dash_core_components'
self._valid_wildcard_attributes = []
self.available_properties = ['id', 'clickData', 'clickAnnotationData', 'hoverData', 'clear_on_unhover', 'selectedData', 'relayoutData', 'restyleData', 'figure', 'style', 'className', 'animate', 'animation_options', 'config', 'loading_state']
self.available_properties = ['id', 'clickData', 'clickAnnotationData', 'hoverData', 'clear_on_unhover', 'selectedData', 'relayoutData', 'extendData', 'restyleData', 'figure', 'style', 'className', 'animate', 'animation_options', 'config', 'loading_state']
self.available_wildcard_properties = []

_explicit_args = kwargs.pop('_explicit_args')
Expand Down
384 changes: 221 additions & 163 deletions dash_core_components/dash_core_components.dev.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dash_core_components/dash_core_components.dev.js.map

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions dash_core_components/dash_core_components.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dash_core_components/dash_core_components.min.js.map

Large diffs are not rendered by default.

227 changes: 119 additions & 108 deletions dash_core_components/metadata.json

Large diffs are not rendered by default.

43 changes: 43 additions & 0 deletions src/components/Graph.react.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,29 @@ class PlotlyGraph extends Component {
);
}

extend(props) {
const {id, extendData} = props;
let updateData, traceIndices, maxPoints;
if (Array.isArray(extendData) && typeof extendData[0] === 'object') {
[updateData, traceIndices, maxPoints] = extendData;
} else {
updateData = extendData;
}

if (!traceIndices) {
function getFirstProp(data) {
return data[Object.keys(data)[0]];
}

function generateIndices(data) {
return Array.from(Array(getFirstProp(data).length).keys());
}
bcliang marked this conversation as resolved.
Show resolved Hide resolved
traceIndices = generateIndices(updateData);
}

return Plotly.extendTraces(id, updateData, traceIndices, maxPoints);
}

bindEvents() {
const {id, setProps, clear_on_unhover} = this.props;

Expand Down Expand Up @@ -211,6 +234,13 @@ class PlotlyGraph extends Component {
if (figureChanged) {
this.plot(nextProps);
}

const extendDataChanged =
this.props.extendData !== nextProps.extendData;
bcliang marked this conversation as resolved.
Show resolved Hide resolved

if (extendDataChanged) {
this.extend(nextProps);
}
}

componentDidUpdate(prevProps) {
Expand Down Expand Up @@ -279,6 +309,18 @@ const graphPropTypes = {
*/
relayoutData: PropTypes.object,

/**
* Data that should be appended to existing traces. Has the form
* `[updateData, traceIndices, maxPoints]`, where `updateData` is an object
* containing the data to extend, `traceIndices` (optional) is an array of
* trace indices that should be extended, and `maxPoints` (optional) is
* either an integer defining the maximum number of points allowed or an
* object with key:value pairs matching `updateData`
* Reference the Plotly.extendTraces API for full usage:
* https://plot.ly/javascript/plotlyjs-function-reference/#plotlyextendtraces
alexcjohnson marked this conversation as resolved.
Show resolved Hide resolved
*/
extendData: PropTypes.object,

/**
* Data from latest restyle event which occurs
* when the user toggles a legend item, changes
Expand Down Expand Up @@ -527,6 +569,7 @@ const graphDefaultProps = {
hoverData: null,
selectedData: null,
relayoutData: null,
extendData: null,
restyleData: null,
figure: {data: [], layout: {}},
animate: false,
Expand Down
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ export {
DatePickerRange,
Upload,
Store,
LogoutButton
LogoutButton,
};
135 changes: 135 additions & 0 deletions test/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1812,6 +1812,141 @@ def render_content(click, prev_graph):
time.sleep(2) # Wait for graph to re-render
self.snapshot('render-empty-graph')

def test_graph_extend_trace(self):
app = dash.Dash(__name__)

def generate_with_id(id, data=None):
if data is None:
data = [{'x': [0, 1, 2, 3, 4],
'y': [0, .5, 1, .5, 0]
}]

return html.Div([html.P(id),
dcc.Graph(id=id,
figure=dict(data=data)),
html.Div(id='output_{}'.format(id))])

figs = ['trace_will_extend',
'trace_will_extend_with_no_indices',
'trace_will_extend_with_max_points']

layout = [generate_with_id(id) for id in figs]

figs.append('trace_will_allow_repeated_extend')
data = [{'y': [0, 0, 0]}]
layout.append(generate_with_id(figs[-1], data))

figs.append('trace_will_extend_selectively')
data = [{'x': [0, 1, 2, 3, 4], 'y': [0, .5, 1, .5, 0]},
{'x': [0, 1, 2, 3, 4], 'y': [1, 1, 1, 1, 1]}]
layout.append(generate_with_id(figs[-1], data))

layout.append(dcc.Interval(
id='interval_extendablegraph_update',
interval=10,
n_intervals=0,
max_intervals=1))

layout.append(dcc.Interval(
id='interval_extendablegraph_extendtwice',
interval=500,
n_intervals=0,
max_intervals=2))

app.layout = html.Div(layout)

@app.callback(Output('trace_will_allow_repeated_extend', 'extendData'),
[Input('interval_extendablegraph_extendtwice', 'n_intervals')])
def trace_will_allow_repeated_extend(n_intervals):
if n_intervals is None or n_intervals < 1:
raise PreventUpdate

return dict(y=[[.1, .2, .3, .4, .5]])

@app.callback(Output('trace_will_extend', 'extendData'),
[Input('interval_extendablegraph_update', 'n_intervals')])
def trace_will_extend(n_intervals):
if n_intervals is None or n_intervals < 1:
raise PreventUpdate

x_new = [5, 6, 7, 8, 9]
y_new = [.1, .2, .3, .4, .5]
return dict(x=[x_new], y=[y_new]), [0]

@app.callback(Output('trace_will_extend_selectively', 'extendData'),
[Input('interval_extendablegraph_update', 'n_intervals')])
def trace_will_extend_selectively(n_intervals):
if n_intervals is None or n_intervals < 1:
raise PreventUpdate

x_new = [5, 6, 7, 8, 9]
y_new = [.1, .2, .3, .4, .5]
return dict(x=[x_new], y=[y_new]), [1]

@app.callback(Output('trace_will_extend_with_no_indices', 'extendData'),
[Input('interval_extendablegraph_update', 'n_intervals')])
def trace_will_extend_with_no_indices(n_intervals):
if n_intervals is None or n_intervals < 1:
raise PreventUpdate

x_new = [5, 6, 7, 8, 9]
y_new = [.1, .2, .3, .4, .5]
return dict(x=[x_new], y=[y_new])

@app.callback(Output('trace_will_extend_with_max_points', 'extendData'),
[Input('interval_extendablegraph_update', 'n_intervals')])
def trace_will_extend_with_max_points(n_intervals):
if n_intervals is None or n_intervals < 1:
raise PreventUpdate

x_new = [5, 6, 7, 8, 9]
y_new = [.1, .2, .3, .4, .5]
return dict(x=[x_new], y=[y_new]), [0], 7

for id in figs:
@app.callback(Output('output_{}'.format(id), 'children'),
[Input(id, 'extendData')],
[State(id, 'figure')])
def display_data(trigger, fig):
return json.dumps(fig['data'])

self.startServer(app)

comparison = json.dumps([
dict(
x=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
y=[0, .5, 1, .5, 0, .1, .2, .3, .4, .5]
)
])
self.wait_for_text_to_equal('#output_trace_will_extend', comparison)
self.wait_for_text_to_equal('#output_trace_will_extend_with_no_indices', comparison)
comparison = json.dumps([
dict(
x=[0, 1, 2, 3, 4],
y=[0, .5, 1, .5, 0]
),
dict(
x=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
y=[1, 1, 1, 1, 1, .1, .2, .3, .4, .5]
)
])
self.wait_for_text_to_equal('#output_trace_will_extend_selectively', comparison)

comparison = json.dumps([
dict(
x=[3, 4, 5, 6, 7, 8, 9],
y=[.5, 0, .1, .2, .3, .4, .5]
)
])
self.wait_for_text_to_equal('#output_trace_will_extend_with_max_points', comparison)

comparison = json.dumps([
dict(
y=[0, 0, 0, .1, .2, .3, .4, .5, .1, .2, .3, .4, .5]
)
])
self.wait_for_text_to_equal('#output_trace_will_allow_repeated_extend', comparison)

def test_storage_component(self):
app = dash.Dash(__name__)

Expand Down