diff --git a/CHANGELOG.md b/CHANGELOG.md index bcc8bb945..0b78cef33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## UNRELEASED +### Fixed +- 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) + ## [1.1.1] - 2019-08-06 - Upgraded plotly.js to 1.49.1 [#595](https://github.com/plotly/dash-core-components/issues/595) - Patch release [1.49.1](https://github.com/plotly/plotly.js/releases/tag/v1.49.1) diff --git a/src/components/Graph.react.js b/src/components/Graph.react.js index b4bce1e4c..4d467dd3a 100644 --- a/src/components/Graph.react.js +++ b/src/components/Graph.react.js @@ -64,67 +64,66 @@ const filterEventData = (gd, eventData, event) => { return filteredEventData; }; -function generateId() { - const charAmount = 36; - const length = 7; - return ( - 'graph-' + - Math.random() - .toString(charAmount) - .substring(2, length) - ); -} - /** * Graph can be used to render any plotly.js-powered data visualization. * * You can define callbacks based on user interaction with Graphs such as * hovering, clicking or selecting */ -const GraphWithDefaults = props => { - const id = props.id ? props.id : generateId(); - return ; -}; - class PlotlyGraph extends Component { constructor(props) { super(props); + this.gd = React.createRef(); this.bindEvents = this.bindEvents.bind(this); this._hasPlotted = false; + this._prevGd = null; this.graphResize = this.graphResize.bind(this); } plot(props) { - const {figure, id, animate, animation_options, config} = props; - const gd = document.getElementById(id); + const {figure, animate, animation_options, config} = props; + const gd = this.gd.current; if ( animate && this._hasPlotted && figure.data.length === gd.data.length ) { - return Plotly.animate(id, figure, animation_options); + return Plotly.animate(gd, figure, animation_options); } - return Plotly.react(id, { + return Plotly.react(gd, { data: figure.data, layout: clone(figure.layout), frames: figure.frames, config: config, }).then(() => { - if (!this._hasPlotted) { - // double-check gd hasn't been unmounted - const gd = document.getElementById(id); - if (gd) { - this.bindEvents(); - Plotly.Plots.resize(gd); - this._hasPlotted = true; + const gd = this.gd.current; + + // double-check gd hasn't been unmounted + if (!gd) { + return; + } + + // in case we've made a new DOM element, transfer events + if(this._hasPlotted && gd !== this._prevGd) { + if(this._prevGd && this._prevGd.removeAllListeners) { + this._prevGd.removeAllListeners(); + Plotly.purge(this._prevGd); } + this._hasPlotted = false; + } + + if (!this._hasPlotted) { + this.bindEvents(); + Plotly.Plots.resize(gd); + this._hasPlotted = true; + this._prevGd = gd; } }); } extend(props) { - const {id, extendData} = props; + const {extendData} = props; let updateData, traceIndices, maxPoints; if (Array.isArray(extendData) && typeof extendData[0] === 'object') { [updateData, traceIndices, maxPoints] = extendData; @@ -143,20 +142,21 @@ class PlotlyGraph extends Component { traceIndices = generateIndices(updateData); } - return Plotly.extendTraces(id, updateData, traceIndices, maxPoints); + const gd = this.gd.current; + return Plotly.extendTraces(gd, updateData, traceIndices, maxPoints); } graphResize() { - const graphDiv = document.getElementById(this.props.id); - if (graphDiv) { - Plotly.Plots.resize(graphDiv); + const gd = this.gd.current; + if (gd) { + Plotly.Plots.resize(gd); } } bindEvents() { - const {id, setProps, clear_on_unhover} = this.props; + const {setProps, clear_on_unhover} = this.props; - const gd = document.getElementById(id); + const gd = this.gd.current; gd.on('plotly_click', eventData => { const clickData = filterEventData(gd, eventData, 'click'); @@ -212,8 +212,10 @@ class PlotlyGraph extends Component { } componentWillUnmount() { - if (this.eventEmitter) { - this.eventEmitter.removeAllListeners(); + const gd = this.gd.current; + if (gd && gd.removeAllListeners) { + gd.removeAllListeners(); + Plotly.purge(gd); } window.removeEventListener('resize', this.graphResize); } @@ -262,6 +264,7 @@ class PlotlyGraph extends Component {