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

Commit cfe5bfa

Browse files
authored
Graph inifinite loop #608 (#621)
fix infinite loop
1 parent 60cf062 commit cfe5bfa

File tree

3 files changed

+89
-24
lines changed

3 files changed

+89
-24
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22
All notable changes to this project will be documented in this file.
33
This project adheres to [Semantic Versioning](http://semver.org/).
44

5+
## Unreleased
6+
### Fixed
7+
8+
- Fixed an infinite loop problem when `Graph` is wrapped by `Loading` component [#608](https://github.com/plotly/dash-core-components/issues/608)
9+
510
## [1.1.2] - 2019-08-27
611
### Fixed
712
- Fixed problems with `Graph` components leaking events and being recreated multiple times if declared with no ID [#604](https://github.com/plotly/dash-core-components/pull/604)

src/components/Graph.react.js

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, {Component} from 'react';
22
import PropTypes from 'prop-types';
3-
import {contains, filter, clone, has, isNil, type, omit} from 'ramda';
3+
import {contains, filter, clone, has, isNil, type, omit, equals} from 'ramda';
44
/* global Plotly:true */
55

66
const filterEventData = (gd, eventData, event) => {
@@ -83,7 +83,6 @@ class PlotlyGraph extends Component {
8383
plot(props) {
8484
const {figure, animate, animation_options, config} = props;
8585
const gd = this.gd.current;
86-
8786
if (
8887
animate &&
8988
this._hasPlotted &&
@@ -98,7 +97,6 @@ class PlotlyGraph extends Component {
9897
config: config,
9998
}).then(() => {
10099
const gd = this.gd.current;
101-
102100
// double-check gd hasn't been unmounted
103101
if (!gd) {
104102
return;
@@ -154,7 +152,14 @@ class PlotlyGraph extends Component {
154152
}
155153

156154
bindEvents() {
157-
const {setProps, clear_on_unhover} = this.props;
155+
const {
156+
setProps,
157+
clear_on_unhover,
158+
relayoutData,
159+
restyleData,
160+
hoverData,
161+
selectedData,
162+
} = this.props;
158163

159164
const gd = this.gd.current;
160165

@@ -172,30 +177,30 @@ class PlotlyGraph extends Component {
172177
setProps({clickAnnotationData});
173178
});
174179
gd.on('plotly_hover', eventData => {
175-
const hoverData = filterEventData(gd, eventData, 'hover');
176-
if (!isNil(hoverData)) {
177-
setProps({hoverData});
180+
const hover = filterEventData(gd, eventData, 'hover');
181+
if (!isNil(hover) && !equals(hover, hoverData)) {
182+
setProps({hoverData: hover});
178183
}
179184
});
180185
gd.on('plotly_selected', eventData => {
181-
const selectedData = filterEventData(gd, eventData, 'selected');
182-
if (!isNil(selectedData)) {
183-
setProps({selectedData});
186+
const selected = filterEventData(gd, eventData, 'selected');
187+
if (!isNil(selected) && !equals(selected, selectedData)) {
188+
setProps({selectedData: selected});
184189
}
185190
});
186191
gd.on('plotly_deselect', () => {
187192
setProps({selectedData: null});
188193
});
189194
gd.on('plotly_relayout', eventData => {
190-
const relayoutData = filterEventData(gd, eventData, 'relayout');
191-
if (!isNil(relayoutData)) {
192-
setProps({relayoutData});
195+
const relayout = filterEventData(gd, eventData, 'relayout');
196+
if (!isNil(relayout) && !equals(relayout, relayoutData)) {
197+
setProps({relayoutData: relayout});
193198
}
194199
});
195200
gd.on('plotly_restyle', eventData => {
196-
const restyleData = filterEventData(gd, eventData, 'restyle');
197-
if (!isNil(restyleData)) {
198-
setProps({restyleData});
201+
const restyle = filterEventData(gd, eventData, 'restyle');
202+
if (!isNil(restyle) && !equals(restyle, restyleData)) {
203+
setProps({restyleData: restyle});
199204
}
200205
});
201206
gd.on('plotly_unhover', () => {
@@ -236,17 +241,11 @@ class PlotlyGraph extends Component {
236241
*/
237242
return;
238243
}
239-
240-
const figureChanged = this.props.figure !== nextProps.figure;
241-
242-
if (figureChanged) {
244+
if (this.props.figure !== nextProps.figure) {
243245
this.plot(nextProps);
244246
}
245247

246-
const extendDataChanged =
247-
this.props.extendData !== nextProps.extendData;
248-
249-
if (extendDataChanged) {
248+
if (this.props.extendData !== nextProps.extendData) {
250249
this.extend(nextProps);
251250
}
252251
}

tests/integration/graph/test_graph_basics.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import pytest
2+
import pandas as pd
3+
import numpy as np
14
import dash
25
import dash_html_components as html
36
import dash_core_components as dcc
7+
from dash.dependencies import Input, Output
8+
import dash.testing.wait as wait
49

510

611
def test_grbs001_graph_without_ids(dash_duo):
@@ -20,3 +25,59 @@ def test_grbs001_graph_without_ids(dash_duo):
2025
assert not dash_duo.wait_for_element(".graph-no-id-2").get_attribute(
2126
"id"
2227
), "the graph should contain no more auto-generated id"
28+
29+
30+
@pytest.mark.DCC608
31+
def test_grbs002_wrapped_graph_has_no_infinite_loop(dash_duo):
32+
33+
df = pd.DataFrame(np.random.randn(50, 50))
34+
figure = {
35+
"data": [
36+
{"x": df.columns, "y": df.index, "z": df.values, "type": "heatmap"}
37+
],
38+
"layout": {"xaxis": {"scaleanchor": "y"}},
39+
}
40+
41+
app = dash.Dash(__name__)
42+
app.layout = html.Div(
43+
style={
44+
"backgroundColor": "red",
45+
"height": "100vmin",
46+
"width": "100vmin",
47+
"overflow": "hidden",
48+
"position": "relative",
49+
},
50+
children=[
51+
dcc.Loading(
52+
children=[
53+
dcc.Graph(
54+
id="graph",
55+
figure=figure,
56+
style={
57+
"position": "absolute",
58+
"top": 0,
59+
"left": 0,
60+
"backgroundColor": "blue",
61+
"width": "100%",
62+
"height": "100%",
63+
"overflow": "hidden",
64+
},
65+
)
66+
]
67+
)
68+
],
69+
)
70+
71+
@app.callback(Output("graph", "figure"), [Input("graph", "relayoutData")])
72+
def selected_df_figure(selection):
73+
figure["data"][0]["x"] = df.columns
74+
figure["data"][0]["y"] = df.index
75+
figure["data"][0]["z"] = df.values
76+
return figure
77+
78+
dash_duo.start_server(app)
79+
80+
wait.until(lambda: dash_duo.driver.title == "Dash", timeout=2)
81+
assert (
82+
len({dash_duo.driver.title for _ in range(20)}) == 1
83+
), "after the first update, there should contain no extra Updating..."

0 commit comments

Comments
 (0)