From 71ba63b69ecdd10af4642751cab9d96643716578 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 19 Dec 2017 11:29:13 -0500 Subject: [PATCH 1/7] merging hub into editor --- examples/async-data/package-lock.json | 23 +++-- examples/async-data/package.json | 1 + examples/async-data/src/App.js | 80 +++++++-------- examples/simple/src/App.js | 49 ++++------ package-lock.json | 136 +++++++++++++------------- package.json | 2 - src/PlotlyEditor.js | 75 ++++++++++++-- 7 files changed, 207 insertions(+), 159 deletions(-) diff --git a/examples/async-data/package-lock.json b/examples/async-data/package-lock.json index f59576073..da8ff9110 100644 --- a/examples/async-data/package-lock.json +++ b/examples/async-data/package-lock.json @@ -2316,6 +2316,16 @@ "sha.js": "2.4.9" } }, + "create-react-class": { + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.2.tgz", + "integrity": "sha1-zx7RXxKq1/FO9fLf4F5sQvke8Co=", + "requires": { + "fbjs": "0.8.16", + "loose-envify": "1.3.1", + "object-assign": "4.1.1" + } + }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -11081,10 +11091,11 @@ } }, "react": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-16.2.0.tgz", - "integrity": "sha512-ZmIomM7EE1DvPEnSFAHZn9Vs9zJl5A9H7el0EGTE6ZbW9FKe/14IYAlPbC8iH25YarEQxZL+E8VW7Mi7kfQrDQ==", + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz", + "integrity": "sha1-26BDSrQ5z+gvEI8PURZjkIF5qnI=", "requires": { + "create-react-class": "15.6.2", "fbjs": "0.8.16", "loose-envify": "1.3.1", "object-assign": "4.1.1", @@ -11117,9 +11128,9 @@ } }, "react-dom": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.2.0.tgz", - "integrity": "sha512-zpGAdwHVn9K0091d+hr+R0qrjoJ84cIBFL2uU60KvWBPfZ7LPSrfqviTxGHWN0sjPZb2hxWzMexwrvJdKePvjg==", + "version": "15.6.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz", + "integrity": "sha1-Qc+t9pO3V/rycIRDodH9WgK+9zA=", "requires": { "fbjs": "0.8.16", "loose-envify": "1.3.1", diff --git a/examples/async-data/package.json b/examples/async-data/package.json index 4e22ce2ad..fb6c06f6d 100644 --- a/examples/async-data/package.json +++ b/examples/async-data/package.json @@ -7,6 +7,7 @@ "plotly.js": "^1.31.2", "react": "^15.6.1", "react-dom": "^15.6.1", + "react-plotly.js": "^1.0.4", "react-scripts": "1.0.17" }, "scripts": { diff --git a/examples/async-data/src/App.js b/examples/async-data/src/App.js index 86be8f9c5..bdcfe703e 100644 --- a/examples/async-data/src/App.js +++ b/examples/async-data/src/App.js @@ -3,7 +3,6 @@ import 'react-plotly.js-editor/lib/react-plotly.js-editor.css'; import 'react-select/dist/react-select.css'; import PlotlyEditor, { EDITOR_ACTIONS, - Hub, dereference, } from 'react-plotly.js-editor'; import React, {Component} from 'react'; @@ -58,37 +57,22 @@ class App extends Component { super(); // A basic starting plotly.js figure object. Instead of assigning - const figure = { + const graphDiv = { data: [{type: 'scatter'}], layout: {title: 'Room readings'}, }; // Store the figure, dataSource and dataSourceOptions in state. - this.state = {figure, dataSources: {}}; - - // Instantiate a Hub object. The hub is a convenience module that updates - // the applies Editor updates to the figure object. After an update it - // will call the onUpdate function with the editor and plot revision numbers. - // We set these in our state and pass them to react-plotly.js-editor and - // react-plotly.js plot component respectively. This is necessary because - // the plotly.js library will mutate the figure object with user interactions. - // The hub listens for events from plotly.js and alerts us to the mutation here. - // The Editor follows the same mutation pattern (for good or ill) and the Hub - // will pick up editor results in the same way. - this.hub = new Hub({ - debug: true, - onUpdate: ({editorRevision, plotRevision}) => - this.setState(prevState => ({ - ...prevState, - editorRevision, - plotRevision, - })), - }); + this.state = { + graphDiv, + editorRevision: 0, + plotRevision: 0, + dataSources: {}, + }; this.getChartingData = this.getChartingData.bind(this); this.setChartingData = this.setChartingData.bind(this); this.setChartingDataOptions = this.setChartingDataOptions.bind(this); - this.onEditorUpdate = this.onEditorUpdate.bind(this); } componentDidMount() { @@ -98,18 +82,18 @@ class App extends Component { this.getChartingDataColumnsNames(); } - componentDidUnmount() { + componentWillUnmount() { backend.off('ChartingDataColumnNames', this.setChartingDataOptions); backend.off('ChartingData', this.setChartingData); } setChartingDataOptions(columnNames) { - const dataSourceOptions = columnNames.map(name => ({ - value: name, - label: name, - })); - this.setState(prevState => ({...prevState, dataSourceOptions})); - this.hub.editorSourcesUpdated(); + this.setState({ + dataSourceOptions: columnNames.map(name => ({ + value: name, + label: name, + })), + }); } getChartingDataColumnsNames() { @@ -127,18 +111,15 @@ class App extends Component { setChartingData({columnName, data}) { if (Array.isArray(data) && data.length) { - const {dataSources, ...otherState} = this.state; - dataSources[columnName] = data; - dereference(this.state.figure.data, dataSources); - this.setState({ - dataSources, - ...otherState, + this.setState(({dataSources, graphDiv}) => { + const newDataSources = {...dataSources, [columnName]: data}; + dereference(graphDiv.data, newDataSources); + return {dataSources, graphDiv}; }); - this.hub.figureUpdated(); } } - onEditorUpdate(event) { + handleEditorUpdateTraces(event) { const {type, payload} = event; if (type === EDITOR_ACTIONS.UPDATE_TRACES) { const {update} = payload; @@ -154,8 +135,14 @@ class App extends Component { } } } + } - this.hub.handleEditorUpdate(event); + handlePlotUpdate(graphDiv) { + this.setState(({editorRevision: x}) => ({editorRevision: x + 1, graphDiv})); + } + + handleEditorUpdate() { + this.setState(({plotRevision: x}) => ({plotRevision: x + 1})); } render() { @@ -165,17 +152,18 @@ class App extends Component { locale="en" dataSources={this.state.dataSources} dataSourceOptions={this.state.dataSourceOptions} - graphDiv={this.hub.graphDiv} - onUpdate={this.onEditorUpdate} - plotly={plotly} + graphDiv={this.state.graphDiv} + onUpdate={this.handleEditorUpdate.bind(this)} + onUpdateTraces={this.handleEditorUpdateTraces.bind(this)} revision={this.state.editorRevision} + plotly={plotly} /> diff --git a/examples/simple/src/App.js b/examples/simple/src/App.js index 75a8e7b2e..17cb855f1 100644 --- a/examples/simple/src/App.js +++ b/examples/simple/src/App.js @@ -1,7 +1,7 @@ import React, {Component} from 'react'; import plotly from 'plotly.js/dist/plotly-basic'; import createPlotComponent from 'react-plotly.js/factory'; -import PlotlyEditor, {Hub, dereference} from 'react-plotly.js-editor'; +import PlotlyEditor, {dereference} from 'react-plotly.js-editor'; import 'react-plotly.js-editor/lib/react-plotly.js-editor.css'; import 'react-select/dist/react-select.css'; @@ -20,38 +20,29 @@ class App extends Component { ]; // A basic starting plotly.js figure object. Instead of assigning - const figure = { + const graphDiv = { data: [{type: 'scatter', xsrc: 'col1', ysrc: 'col2'}], layout: {title: 'Room readings'}, }; - dereference(figure.data, dataSources); + dereference(graphDiv.data, dataSources); // Store the figure, dataSource and dataSourceOptions in state. this.state = { - figure, + graphDiv, + editorRevision: 0, + plotRevision: 0, dataSources, dataSourceOptions, }; + } + + handlePlotUpdate(graphDiv) { + this.setState(({editorRevision: x}) => ({editorRevision: x + 1, graphDiv})); + } - // Instantiate a Hub object. The hub is a convenience module that updates - // the applies Editor updates to the figure object. After an update it - // will call the onUpdate function with the editor and plot revision numbers. - // We set these in our state and pass them to react-plotly.js-editor and - // react-plotly.js plot component respectively. This is necessary because - // the plotly.js library will mutate the figure object with user interactions. - // The hub listens for events from plotly.js and alerts us to the mutation here. - // The Editor follows the same mutation pattern (for good or ill) and the Hub - // will pick up editor results in the same way. - this.hub = new Hub({ - debug: true, - onUpdate: ({editorRevision, plotRevision}) => - this.setState(prevState => ({ - ...prevState, - editorRevision, - plotRevision, - })), - }); + handleEditorUpdate() { + this.setState(({plotRevision: x}) => ({plotRevision: x + 1})); } render() { @@ -61,18 +52,18 @@ class App extends Component { locale="en" dataSources={this.state.dataSources} dataSourceOptions={this.state.dataSourceOptions} - graphDiv={this.hub.graphDiv} - onUpdate={this.hub.handleEditorUpdate} - plotly={plotly} + graphDiv={this.state.graphDiv} + onUpdate={this.handleEditorUpdate.bind(this)} revision={this.state.editorRevision} + plotly={plotly} />
diff --git a/package-lock.json b/package-lock.json index 15fa8ad98..e67e2ec73 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,28 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "3d-view": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/3d-view/-/3d-view-2.0.0.tgz", + "integrity": "sha1-gxrpQtdQjFCAHj4G+v4ejFdOF74=", + "requires": { + "matrix-camera-controller": "2.1.3", + "orbit-camera-controller": "4.0.0", + "turntable-camera-controller": "3.0.1" + } + }, + "3d-view-controls": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/3d-view-controls/-/3d-view-controls-2.2.0.tgz", + "integrity": "sha1-RK7JxEjCe+NLPdUR/5ICq2FQ3KU=", + "requires": { + "3d-view": "2.0.0", + "mouse-change": "1.4.0", + "mouse-event-offset": "3.0.2", + "mouse-wheel": "1.2.0", + "right-now": "1.0.0" + } + }, "@babel/code-frame": { "version": "7.0.0-beta.32", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.32.tgz", @@ -160,27 +182,20 @@ "integrity": "sha1-OWs1r4JvpmqtRyyMt7jV4nf05tg=", "dev": true }, - "3d-view": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/3d-view/-/3d-view-2.0.0.tgz", - "integrity": "sha1-gxrpQtdQjFCAHj4G+v4ejFdOF74=", + "JSONStream": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", + "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", + "dev": true, "requires": { - "matrix-camera-controller": "2.1.3", - "orbit-camera-controller": "4.0.0", - "turntable-camera-controller": "3.0.1" + "jsonparse": "1.3.1", + "through": "2.3.8" } }, - "3d-view-controls": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/3d-view-controls/-/3d-view-controls-2.2.0.tgz", - "integrity": "sha1-RK7JxEjCe+NLPdUR/5ICq2FQ3KU=", - "requires": { - "3d-view": "2.0.0", - "mouse-change": "1.4.0", - "mouse-event-offset": "3.0.2", - "mouse-wheel": "1.2.0", - "right-now": "1.0.0" - } + "JSV": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", + "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=" }, "a-big-triangle": { "version": "1.0.3", @@ -386,7 +401,7 @@ "array-bounds": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-bounds/-/array-bounds-1.0.1.tgz", - "integrity": "sha512-8wdW3ZGk6UjMPJx/glyEt0sLzzwAE1bhToPsO1W2pbpR2gULyxe3BjSiuJFheP50T/GgODVPz2fuMUmIywt8cQ==" + "integrity": "sha1-2hE1a04Y4HWk8MhuHxeaZ7fX6jE=" }, "array-equal": { "version": "1.0.0", @@ -1714,9 +1729,9 @@ "integrity": "sha1-+GzWzvT1MAyOY+B6TVEvZfv/RTE=", "dev": true, "requires": { + "JSONStream": "1.3.1", "combine-source-map": "0.7.2", "defined": "1.0.0", - "JSONStream": "1.3.1", "through2": "2.0.3", "umd": "3.0.1" } @@ -1744,6 +1759,7 @@ "integrity": "sha1-C7vOUhrNbk0dVNjpNlAI77hanMU=", "dev": true, "requires": { + "JSONStream": "1.3.1", "assert": "1.4.1", "browser-pack": "6.0.2", "browser-resolve": "1.11.2", @@ -1765,7 +1781,6 @@ "https-browserify": "1.0.0", "inherits": "2.0.3", "insert-module-globals": "7.0.1", - "JSONStream": "1.3.1", "labeled-stream-splicer": "2.0.0", "module-deps": "4.1.1", "os-browserify": "0.3.0", @@ -2178,7 +2193,7 @@ "color-id": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/color-id/-/color-id-1.1.0.tgz", - "integrity": "sha512-2iRtAn6dC/6/G7bBIo0uupVrIne1NsQJvJxZOBCzQOfk7jRq97feaDZ3RdzuHakRXXnHGNwglto3pqtRx1sX0g==", + "integrity": "sha1-XpFZuZpzrJj3SCDLmKFf3j1+A0w=", "requires": { "clamp": "1.0.1" } @@ -2220,7 +2235,7 @@ "colormap": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/colormap/-/colormap-2.3.0.tgz", - "integrity": "sha512-Mkk6mQUMbCleXEeStFm2xLwv5zbRakZMUFB1T1+iNEv58VKBByfPwYIjMQDwSRmXNM1gvo5y3WTYAhmdMn/rbg==", + "integrity": "sha1-9yXHV8XG8JQKU0KnI8aARKwGzBU=", "requires": { "lerp": "1.0.3" } @@ -2589,7 +2604,7 @@ "d3-array": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.1.tgz", - "integrity": "sha512-CyINJQ0SOUHojDdFDH4JEM0552vCR1utGyLHegJHyYH0JyCpSeTPxi4OBqHMA2jJZq4NH782LtaJWBImqI/HBw==" + "integrity": "sha1-0coz3i9qwx76244FCgIdfiOW1dw=" }, "d3-collection": { "version": "1.0.4", @@ -2609,7 +2624,7 @@ "d3-force": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.1.0.tgz", - "integrity": "sha512-2HVQz3/VCQs0QeRNZTYb7GxoUCeb6bOzMp/cGcLa87awY9ZsPvXOGeZm0iaGBjXic6I1ysKwMn+g+5jSAdzwcg==", + "integrity": "sha1-zr88aU8QePzD1Nr45Wey+9cNTqM=", "requires": { "d3-collection": "1.0.4", "d3-dispatch": "1.0.3", @@ -2633,7 +2648,7 @@ "d3-timer": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.7.tgz", - "integrity": "sha512-vMZXR88XujmG/L5oB96NNKH5lCWwiLM/S2HyyAQLcjWJCloK5shxta4CwOFYLZoY3AWX73v8Lgv4cCAdWtRmOA==" + "integrity": "sha1-35ZQylh/bJZgf/TmDMOCKejdhTE=" }, "dashdash": { "version": "1.14.1", @@ -2938,7 +2953,7 @@ "duplexify": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.5.1.tgz", - "integrity": "sha512-j5goxHTwVED1Fpe5hh3q9R93Kip0Bg2KVAt4f8CEYM3UEwYcPSvWbXaUQOzdX/HtiNomipv+gU7ASQPDbV7pGQ==", + "integrity": "sha1-ThUWvmiDi8kKSZlPCzmm5ZYL780=", "requires": { "end-of-stream": "1.4.0", "inherits": "2.0.3", @@ -2949,7 +2964,7 @@ "earcut": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/earcut/-/earcut-2.1.2.tgz", - "integrity": "sha512-ji2b8qOVwK4WChYTbpKo983518wEqY2wrpkd85Us/LLw+3O7G0jGvGbHgQERuovrv3Cop9cEpiNkhqVQSkgTtA==" + "integrity": "sha1-VCrdDKOntxNFJyDh0FOTfT2vN4Q=" }, "ecc-jsbn": { "version": "0.1.1", @@ -3682,7 +3697,7 @@ "font-atlas-sdf": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/font-atlas-sdf/-/font-atlas-sdf-1.3.3.tgz", - "integrity": "sha512-GxUpcdkdoHgC3UrpMuA7JmG1Ty/MY0BhfmV8r7ZSv3bkqBY5vmRIjcj7Pg8iqj20B03vlU6fUhdpyIgEo/Z35w==", + "integrity": "sha1-gyPxNsadc6I1qoxq2mQOWPGAuMA=", "requires": { "optical-properties": "1.0.0", "tiny-sdf": "1.0.2" @@ -4525,14 +4540,6 @@ } } }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, "string-width": { "version": "1.0.2", "bundled": true, @@ -4543,6 +4550,14 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, "stringstream": { "version": "0.0.5", "bundled": true, @@ -7118,10 +7133,10 @@ "integrity": "sha1-wDv04BywhtW15azorQr+eInWOMM=", "dev": true, "requires": { + "JSONStream": "1.3.1", "combine-source-map": "0.7.2", "concat-stream": "1.5.2", "is-buffer": "1.1.6", - "JSONStream": "1.3.1", "lexical-scope": "1.2.0", "process": "0.11.10", "through2": "2.0.3", @@ -8342,16 +8357,6 @@ "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", "dev": true }, - "JSONStream": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.1.tgz", - "integrity": "sha1-cH92HgHa6eFvG8+TcDt4xwlmV5o=", - "dev": true, - "requires": { - "jsonparse": "1.3.1", - "through": "2.3.8" - } - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -8363,11 +8368,6 @@ "verror": "1.10.0" } }, - "JSV": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz", - "integrity": "sha1-0Hf2glVx+CEy+d/67Vh7QCn+/1c=" - }, "jsx-ast-utils": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.0.1.tgz", @@ -8962,6 +8962,7 @@ "integrity": "sha1-IyFYM/HaE/1gbMuAh7RIUty4If0=", "dev": true, "requires": { + "JSONStream": "1.3.1", "browser-resolve": "1.11.2", "cached-path-relative": "1.0.1", "concat-stream": "1.5.2", @@ -8969,7 +8970,6 @@ "detective": "4.6.0", "duplexer2": "0.1.4", "inherits": "2.0.3", - "JSONStream": "1.3.1", "parents": "1.0.1", "readable-stream": "2.3.3", "resolve": "1.5.0", @@ -9545,7 +9545,7 @@ "object-inspect": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.3.0.tgz", - "integrity": "sha512-OHHnLgLNXpM++GnJRyyhbr2bwl3pPVm4YvaraHrRvDt/N3r+s/gDVHciA7EJBTkijKXj61ssgSAikq1fb0IBRg==" + "integrity": "sha1-Wx645nQuLugzQqY3A02ESSi6L20=" }, "object-is": { "version": "1.0.1", @@ -9623,7 +9623,7 @@ "optical-properties": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/optical-properties/-/optical-properties-1.0.0.tgz", - "integrity": "sha512-XnBQYbIIzDVr7U3L7d3xyAEqp1W+HTkqmw/G4L/Ae/+dq57bT1jqW2uDwV0wCUzO8gsTDIZhGQsGrMb17VSkEA==" + "integrity": "sha1-w6aUu6t8xFhwcIhsR/Q8jDpszq4=" }, "optimist": { "version": "0.6.1", @@ -9967,8 +9967,8 @@ "resolved": "https://registry.npmjs.org/plotly.js/-/plotly.js-1.31.2.tgz", "integrity": "sha1-G0EAvGCVf+dYnOcs/M1O5Jnfrts=", "requires": { - "@plotly/d3-sankey": "0.5.0", "3d-view": "2.0.0", + "@plotly/d3-sankey": "0.5.0", "alpha-shape": "1.0.0", "color-rgba": "1.1.1", "convex-hull": "1.0.3", @@ -11689,14 +11689,6 @@ "readable-stream": "2.3.3" } }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-length": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", @@ -11761,6 +11753,14 @@ "function-bind": "1.1.1" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "requires": { + "safe-buffer": "5.1.1" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -11929,7 +11929,7 @@ "tape": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/tape/-/tape-4.8.0.tgz", - "integrity": "sha512-TWILfEnvO7I8mFe35d98F6T5fbLaEtbFTG/lxWvid8qDfFTxt19EBijWmB4j3+Hoh5TfHE2faWs73ua+EphuBA==", + "integrity": "sha1-9qn+xBzFCh3lD6M2A6tYCZH2Bo4=", "requires": { "deep-equal": "1.0.1", "defined": "1.0.0", @@ -12354,7 +12354,7 @@ "unassertify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/unassertify/-/unassertify-2.1.0.tgz", - "integrity": "sha512-CB3C3vbOwrZydRuGdU8H421r4/qhM8RLuEOo3G+wEFf7kDP4TR+7oDuj1yOik5pUzXMaJmzxICM7akupP1AlJw==", + "integrity": "sha1-awer9cZZi6OFKiemdsqtHfUmqW0=", "requires": { "acorn": "5.2.1", "convert-source-map": "1.5.1", @@ -12572,7 +12572,7 @@ "webworkify": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/webworkify/-/webworkify-1.5.0.tgz", - "integrity": "sha512-AMcUeyXAhbACL8S2hqqdqOLqvJ8ylmIbNwUIqQujRSouf4+eUFaXbG6F1Rbu+srlJMmxQWsiU7mOJi0nMBfM1g==" + "integrity": "sha1-c0rYendN5uvdVG4dPgJ9pbj0pCw=" }, "wgs84": { "version": "0.0.0", diff --git a/package.json b/package.json index f64d573cc..3f3c8dd99 100644 --- a/package.json +++ b/package.json @@ -63,8 +63,6 @@ "mkdirp": "^0.5.1", "node-sass": "^4.7.1", "prettier": "1.8.2", - "react": "^16.2.0", - "react-dom": "^16.2.0", "react-test-renderer": "^16.2.0", "uglify-js": "^3.0.26" }, diff --git a/src/PlotlyEditor.js b/src/PlotlyEditor.js index 9e82586e5..24673efa7 100644 --- a/src/PlotlyEditor.js +++ b/src/PlotlyEditor.js @@ -4,6 +4,9 @@ import React, {Component} from 'react'; import dictionaries from './locales'; import {bem} from './lib'; import {noShame} from './shame'; +import {EDITOR_ACTIONS} from './lib/constants'; +import isNumeric from 'fast-isnumeric'; +import nestedProperty from 'plotly.js/src/lib/nested_property'; class PlotlyEditor extends Component { constructor(props, context) { @@ -18,9 +21,15 @@ class PlotlyEditor extends Component { } shouldComponentUpdate(nextProps) { - const nextRevision = nextProps.revision; - const currRevision = this.props.revision; - return nextRevision === void 0 || nextRevision !== currRevision; + if ( + nextProps.revision === void 0 || + nextProps.revision !== this.props.revision || + nextProps.dataSources !== this.props.dataSources || + nextProps.dataSourceOptions !== this.props.dataSourceOptions + ) { + return true; + } + return false; } getChildContext() { @@ -35,23 +44,72 @@ class PlotlyEditor extends Component { graphDiv: gd, layout: gd.layout, locale: this.props.locale, - onUpdate: this.updateProp.bind(this), + onUpdate: this.handleUpdate.bind(this), plotSchema: this.plotSchema, plotly: this.props.plotly, }; } - updateProp(event) { + handleUpdate({type, payload}) { const {graphDiv} = this.props; - if (this.props.onUpdate) { - this.props.onUpdate({graphDiv, ...event}); + + switch (type) { + case EDITOR_ACTIONS.UPDATE_TRACES: + if (this.props.onUpdateTraces) { + this.props.onUpdateTraces({type, payload}); + } + for (let i = 0; i < payload.traceIndexes.length; i++) { + for (const attr in payload.update) { + const traceIndex = payload.traceIndexes[i]; + const prop = nestedProperty(graphDiv.data[traceIndex], attr); + const value = payload.update[attr]; + if (value !== void 0) { + prop.set(value); + } + } + } + this.props.onUpdate(); + break; + + case EDITOR_ACTIONS.UPDATE_LAYOUT: + for (const attr in payload.update) { + const prop = nestedProperty(graphDiv.layout, attr); + const value = payload.update[attr]; + if (value !== void 0) { + prop.set(value); + } + } + this.props.onUpdate(); + break; + + case EDITOR_ACTIONS.ADD_TRACE: + graphDiv.data.push({x: [], y: []}); + this.props.onUpdate(); + break; + + case EDITOR_ACTIONS.DELETE_TRACE: + if (payload.traceIndexes && payload.traceIndexes.length) { + graphDiv.data.splice(payload[0], 1); + this.props.onUpdate(); + } + break; + + case EDITOR_ACTIONS.DELETE_ANNOTATION: + if (isNumeric(payload.annotationIndex)) { + graphDiv.layout.annotations.splice(payload.annotationIndex, 1); + this.props.onUpdate(); + } + break; + + default: + throw new Error('must specify an action type to handleEditorUpdate'); } } render() { return (
- {this.props.graphDiv && + {this.props.graphDiv._fullLayout && (this.props.children ? this.props.children : )}
); @@ -66,6 +124,7 @@ PlotlyEditor.propTypes = { locale: PropTypes.string, revision: PropTypes.any, onUpdate: PropTypes.func, + onUpdateTraces: PropTypes.func, plotly: PropTypes.object, }; From e1ddddc4a86a648bc1ea2fba264a9d5b7e176128 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 19 Dec 2017 13:14:57 -0500 Subject: [PATCH 2/7] adding react back in --- package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/package.json b/package.json index 3f3c8dd99..f64d573cc 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,8 @@ "mkdirp": "^0.5.1", "node-sass": "^4.7.1", "prettier": "1.8.2", + "react": "^16.2.0", + "react-dom": "^16.2.0", "react-test-renderer": "^16.2.0", "uglify-js": "^3.0.26" }, From 83f8d07b731d34b9c6bec63135c26e421ceda445 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 19 Dec 2017 13:23:10 -0500 Subject: [PATCH 3/7] fixing some tests --- examples/async-data/src/App.js | 25 +++++++++---------------- src/PlotlyEditor.js | 5 +++-- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/examples/async-data/src/App.js b/examples/async-data/src/App.js index bdcfe703e..59705068c 100644 --- a/examples/async-data/src/App.js +++ b/examples/async-data/src/App.js @@ -1,10 +1,7 @@ import './App.css'; import 'react-plotly.js-editor/lib/react-plotly.js-editor.css'; import 'react-select/dist/react-select.css'; -import PlotlyEditor, { - EDITOR_ACTIONS, - dereference, -} from 'react-plotly.js-editor'; +import PlotlyEditor, {dereference} from 'react-plotly.js-editor'; import React, {Component} from 'react'; import createPlotComponent from 'react-plotly.js/factory'; import ee from 'event-emitter'; @@ -119,18 +116,14 @@ class App extends Component { } } - handleEditorUpdateTraces(event) { - const {type, payload} = event; - if (type === EDITOR_ACTIONS.UPDATE_TRACES) { - const {update} = payload; - if (update) { - for (const key in update) { - if (key.substr(key.length - 3) === 'src') { - const columnId = update[key]; - const data = this.state.dataSources[columnId]; - if (!Array.isArray(data).length || !data.length) { - this.getChartingData(columnId); - } + handleEditorUpdateTraces({update}) { + if (update) { + for (const key in update) { + if (key.substr(key.length - 3) === 'src') { + const columnId = update[key]; + const data = this.state.dataSources[columnId]; + if (!Array.isArray(data).length || !data.length) { + this.getChartingData(columnId); } } } diff --git a/src/PlotlyEditor.js b/src/PlotlyEditor.js index 24673efa7..4d6e76eb5 100644 --- a/src/PlotlyEditor.js +++ b/src/PlotlyEditor.js @@ -56,7 +56,7 @@ class PlotlyEditor extends Component { switch (type) { case EDITOR_ACTIONS.UPDATE_TRACES: if (this.props.onUpdateTraces) { - this.props.onUpdateTraces({type, payload}); + this.props.onUpdateTraces(payload); } for (let i = 0; i < payload.traceIndexes.length; i++) { for (const attr in payload.update) { @@ -109,7 +109,8 @@ class PlotlyEditor extends Component { render() { return (
- {this.props.graphDiv._fullLayout && + {this.props.graphDiv && + this.props.graphDiv._fullLayout && (this.props.children ? this.props.children : )}
); From 947a74eae89ead529ba27505b57d6a2eae7009fc Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 19 Dec 2017 13:33:20 -0500 Subject: [PATCH 4/7] bugfix --- examples/async-data/src/App.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/async-data/src/App.js b/examples/async-data/src/App.js index 59705068c..61359b4ef 100644 --- a/examples/async-data/src/App.js +++ b/examples/async-data/src/App.js @@ -111,7 +111,7 @@ class App extends Component { this.setState(({dataSources, graphDiv}) => { const newDataSources = {...dataSources, [columnName]: data}; dereference(graphDiv.data, newDataSources); - return {dataSources, graphDiv}; + return {dataSources: newDataSources, graphDiv}; }); } } From aec613c0fff29a3c14c709755351c07e21dedc8d Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 19 Dec 2017 14:06:38 -0500 Subject: [PATCH 5/7] fixing all tests --- src/PlotlyEditor.js | 36 +++- .../__tests__/AnnotationAccordion-test.js | 16 +- .../containers/__tests__/Fold-test.js | 8 +- .../containers/__tests__/Layout-test.js | 10 +- .../__tests__/TraceAccordion-test.js | 17 +- .../fields/__tests__/AnnotationRef-test.js | 18 +- .../fields/__tests__/DataSelector-test.js | 6 +- .../fields/__tests__/TraceSelector-test.js | 12 +- src/hub.js | 165 ------------------ src/index.js | 2 - .../connectAnnotationToLayout-test.js | 9 +- src/lib/__tests__/connectLayoutToPlot-test.js | 10 +- src/lib/__tests__/connectTraceToPlot-test.js | 13 +- src/lib/__tests__/multiValued-test.js | 9 +- .../nestedContainerConnections-test.js | 34 ++-- 15 files changed, 100 insertions(+), 265 deletions(-) delete mode 100644 src/hub.js diff --git a/src/PlotlyEditor.js b/src/PlotlyEditor.js index 4d6e76eb5..080072870 100644 --- a/src/PlotlyEditor.js +++ b/src/PlotlyEditor.js @@ -68,10 +68,15 @@ class PlotlyEditor extends Component { } } } - this.props.onUpdate(); + if (this.props.onUpdate) { + this.props.onUpdate(); + } break; case EDITOR_ACTIONS.UPDATE_LAYOUT: + if (this.props.onUpdateLayout) { + this.props.onUpdateLayout(payload); + } for (const attr in payload.update) { const prop = nestedProperty(graphDiv.layout, attr); const value = payload.update[attr]; @@ -79,25 +84,42 @@ class PlotlyEditor extends Component { prop.set(value); } } - this.props.onUpdate(); + if (this.props.onUpdate) { + this.props.onUpdate(); + } break; case EDITOR_ACTIONS.ADD_TRACE: + if (this.props.onAddTrace) { + this.props.onAddTrace(payload); + } graphDiv.data.push({x: [], y: []}); - this.props.onUpdate(); + if (this.props.onUpdate) { + this.props.onUpdate(); + } break; case EDITOR_ACTIONS.DELETE_TRACE: + if (this.props.onDeleteTrace) { + this.props.onDeleteTrace(payload); + } if (payload.traceIndexes && payload.traceIndexes.length) { graphDiv.data.splice(payload[0], 1); - this.props.onUpdate(); + if (this.props.onUpdate) { + this.props.onUpdate(); + } } break; case EDITOR_ACTIONS.DELETE_ANNOTATION: + if (this.props.onDeleteAnnotation) { + this.props.onDeleteAnnotation(payload); + } if (isNumeric(payload.annotationIndex)) { graphDiv.layout.annotations.splice(payload.annotationIndex, 1); - this.props.onUpdate(); + if (this.props.onUpdate) { + this.props.onUpdate(); + } } break; @@ -126,6 +148,10 @@ PlotlyEditor.propTypes = { revision: PropTypes.any, onUpdate: PropTypes.func, onUpdateTraces: PropTypes.func, + onUpdateLayout: PropTypes.func, + onAddTrace: PropTypes.func, + onDeleteTrace: PropTypes.func, + onDeleteAnnotation: PropTypes.func, plotly: PropTypes.object, }; diff --git a/src/components/containers/__tests__/AnnotationAccordion-test.js b/src/components/containers/__tests__/AnnotationAccordion-test.js index 3f28d3eee..80c9e8a7a 100644 --- a/src/components/containers/__tests__/AnnotationAccordion-test.js +++ b/src/components/containers/__tests__/AnnotationAccordion-test.js @@ -1,7 +1,6 @@ import React from 'react'; import {AnnotationAccordion, Panel, Fold} from '..'; import {Numeric} from '../../fields'; -import {EDITOR_ACTIONS} from '../../../lib/constants'; import {TestEditor, fixtures, mount} from '../../../lib/test-utils'; import {connectLayoutToPlot} from '../../../lib'; @@ -30,9 +29,9 @@ describe('', () => { it('can add annotations', () => { const fixture = fixtures.scatter(); - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const editor = mount( - + @@ -43,8 +42,7 @@ describe('', () => { editor.find('.panel__add-button').simulate('click'); - const {type, payload} = onUpdate.mock.calls[0][0]; - expect(type).toBe(EDITOR_ACTIONS.UPDATE_LAYOUT); + const payload = onUpdateLayout.mock.calls[0][0]; expect(payload.update).toEqual({'annotations[0]': {text: 'new text'}}); }); @@ -52,9 +50,9 @@ describe('', () => { const fixture = fixtures.scatter({ layout: {annotations: [{text: 'hodor'}, {text: 'rodoh'}]}, }); - const onUpdate = jest.fn(); + const onDeleteAnnotation = jest.fn(); const editor = mount( - + @@ -67,7 +65,7 @@ describe('', () => { .at(0) .simulate('click'); - const update = onUpdate.mock.calls[0][0]; - expect(update.type).toBe(EDITOR_ACTIONS.DELETE_ANNOTATION); + const update = onDeleteAnnotation.mock.calls[0][0]; + expect(update.annotationIndex).toBe(0); }); }); diff --git a/src/components/containers/__tests__/Fold-test.js b/src/components/containers/__tests__/Fold-test.js index c007f65a7..6a83b1ebb 100644 --- a/src/components/containers/__tests__/Fold-test.js +++ b/src/components/containers/__tests__/Fold-test.js @@ -1,7 +1,6 @@ import {Numeric} from '../../fields'; import {Fold} from '..'; import React from 'react'; -import {EDITOR_ACTIONS} from '../../../lib/constants'; import {TestEditor, fixtures, mount} from '../../../lib/test-utils'; import {connectTraceToPlot} from '../../../lib'; @@ -29,9 +28,9 @@ describe('', () => { }); it('calls deleteContainer when function present', () => { - const onUpdate = jest.fn(); + const onDeleteTrace = jest.fn(); mount( - + @@ -40,8 +39,7 @@ describe('', () => { .find('.fold__delete') .simulate('click'); - const {type, payload} = onUpdate.mock.calls[0][0]; - expect(type).toBe(EDITOR_ACTIONS.DELETE_TRACE); + const payload = onDeleteTrace.mock.calls[0][0]; expect(payload).toEqual({traceIndexes: [0]}); }); }); diff --git a/src/components/containers/__tests__/Layout-test.js b/src/components/containers/__tests__/Layout-test.js index 3b728876e..bfa33a42d 100644 --- a/src/components/containers/__tests__/Layout-test.js +++ b/src/components/containers/__tests__/Layout-test.js @@ -2,7 +2,6 @@ import {Numeric} from '../../fields'; import {Fold, Panel, Section} from '..'; import NumericInput from '../../widgets/NumericInput'; import React from 'react'; -import {EDITOR_ACTIONS} from '../../../lib/constants'; import {TestEditor, fixtures} from '../../../lib/test-utils'; import {connectLayoutToPlot} from '../../../lib'; import {mount} from 'enzyme'; @@ -27,10 +26,10 @@ Layouts.forEach(Layout => { }); it(`sends updates to gd._layout`, () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const wrapper = mount( @@ -43,9 +42,8 @@ Layouts.forEach(Layout => { const widthUpdate = 200; wrapper.prop('onChange')(widthUpdate); - const event = onUpdate.mock.calls[0][0]; - expect(event.type).toBe(EDITOR_ACTIONS.UPDATE_LAYOUT); - expect(event.payload).toEqual({update: {width: widthUpdate}}); + const payload = onUpdateLayout.mock.calls[0][0]; + expect(payload).toEqual({update: {width: widthUpdate}}); }); }); }); diff --git a/src/components/containers/__tests__/TraceAccordion-test.js b/src/components/containers/__tests__/TraceAccordion-test.js index 0a8297001..ee0b212fc 100644 --- a/src/components/containers/__tests__/TraceAccordion-test.js +++ b/src/components/containers/__tests__/TraceAccordion-test.js @@ -1,7 +1,6 @@ import React from 'react'; import {TraceAccordion, Fold} from '..'; import {Numeric} from '../../fields'; -import {EDITOR_ACTIONS} from '../../../lib/constants'; import {TestEditor, fixtures, mount} from '../../../lib/test-utils'; describe('', () => { @@ -21,9 +20,9 @@ describe('', () => { it('can add traces', () => { const fixture = fixtures.scatter(); - const onUpdate = jest.fn(); + const onAddTrace = jest.fn(); const editor = mount( - + @@ -32,15 +31,14 @@ describe('', () => { editor.find('.panel__add-button').simulate('click'); - const update = onUpdate.mock.calls[0][0]; - expect(update.type).toBe(EDITOR_ACTIONS.ADD_TRACE); + expect(onAddTrace).toBeCalled(); }); it('can delete traces', () => { const fixture = fixtures.scatter({data: [{name: 'hodor'}]}); - const onUpdate = jest.fn(); + const onDeleteTrace = jest.fn(); const editor = mount( - + @@ -52,7 +50,8 @@ describe('', () => { .at(0) .simulate('click'); - const update = onUpdate.mock.calls[0][0]; - expect(update.type).toBe(EDITOR_ACTIONS.DELETE_TRACE); + expect(onDeleteTrace).toBeCalled(); + const update = onDeleteTrace.mock.calls[0][0]; + expect(update.traceIndexes[0]).toBe(0); }); }); diff --git a/src/components/fields/__tests__/AnnotationRef-test.js b/src/components/fields/__tests__/AnnotationRef-test.js index 68163782e..7fc63504c 100644 --- a/src/components/fields/__tests__/AnnotationRef-test.js +++ b/src/components/fields/__tests__/AnnotationRef-test.js @@ -32,15 +32,15 @@ describe('', () => { }); it('sends update for a[x|y]ref attr on [x|y]ref change', () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const fixtureProps = fixtures.scatter({ layout: {annotations: [{text: 'thor', ayref: 'y'}]}, }); - const drop = render({onUpdate, ...fixtureProps}).find(DropdownWidget); + const drop = render({onUpdateLayout, ...fixtureProps}).find(DropdownWidget); drop.prop('onChange')('y2'); - const {payload: {update}} = onUpdate.mock.calls[0][0]; + const {update} = onUpdateLayout.mock.calls[0][0]; expect(update).toEqual({ 'annotations[0].ayref': 'y2', 'annotations[0].yref': 'y2', @@ -48,28 +48,28 @@ describe('', () => { }); it('does not send update for a[x|y]ref attr on "paper" change', () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const fixtureProps = fixtures.scatter({ layout: {annotations: [{text: 'thor', ayref: 'y'}]}, }); - const drop = render({onUpdate, ...fixtureProps}).find(DropdownWidget); + const drop = render({onUpdateLayout, ...fixtureProps}).find(DropdownWidget); drop.prop('onChange')('paper'); - const {payload: {update}} = onUpdate.mock.calls[0][0]; + const {update} = onUpdateLayout.mock.calls[0][0]; expect(update).toEqual({ 'annotations[0].yref': 'paper', }); }); it('does not send update for a[x|y]ref when a[x|y]ref is pixel', () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const fixtureProps = fixtures.scatter({ layout: {annotations: [{text: 'thor', yref: 'y', ayref: 'pixel'}]}, }); - const drop = render({onUpdate, ...fixtureProps}).find(DropdownWidget); + const drop = render({onUpdateLayout, ...fixtureProps}).find(DropdownWidget); drop.prop('onChange')('y2'); - const {payload: {update}} = onUpdate.mock.calls[0][0]; + const {update} = onUpdateLayout.mock.calls[0][0]; expect(update).toEqual({ 'annotations[0].yref': 'y2', }); diff --git a/src/components/fields/__tests__/DataSelector-test.js b/src/components/fields/__tests__/DataSelector-test.js index b0c39a951..cc84b5a87 100644 --- a/src/components/fields/__tests__/DataSelector-test.js +++ b/src/components/fields/__tests__/DataSelector-test.js @@ -35,10 +35,10 @@ describe('DataSelector', () => { it('uses gd.data dataSrc value not fullValue when arrayOk', () => {}); it('calls updatePlot with srcAttr and data when present', () => { - const onUpdate = jest.fn(); - const wrapper = render({onUpdate}).find(DropdownWidget); + const onUpdateTraces = jest.fn(); + const wrapper = render({onUpdateTraces}).find(DropdownWidget); wrapper.prop('onChange')('y1'); - expect(onUpdate.mock.calls[0][0].payload).toEqual({ + expect(onUpdateTraces.mock.calls[0][0]).toEqual({ update: {xsrc: 'y1', x: [2, 3, 4]}, traceIndexes: [0], }); diff --git a/src/components/fields/__tests__/TraceSelector-test.js b/src/components/fields/__tests__/TraceSelector-test.js index 8431456ee..f9db1ca1a 100644 --- a/src/components/fields/__tests__/TraceSelector-test.js +++ b/src/components/fields/__tests__/TraceSelector-test.js @@ -46,10 +46,10 @@ describe('TraceSelector', () => { }); it('updates type=scatter mode=lines when type=line', () => { - const onUpdate = jest.fn(); + const onUpdateTraces = jest.fn(); const editorProps = { ...fixtures.scatter({data: [{type: 'scatter', mode: 'markers'}]}), - onUpdate, + onUpdateTraces, plotly, }; const wrapper = mount( @@ -63,7 +63,7 @@ describe('TraceSelector', () => { const innerDropdown = wrapper.find(Dropdown); innerDropdown.prop('onChange')('line'); - const {payload} = onUpdate.mock.calls[0][0]; + const payload = onUpdateTraces.mock.calls[0][0]; expect(payload.update).toEqual({ fill: 'none', mode: 'lines', @@ -72,10 +72,10 @@ describe('TraceSelector', () => { }); it('updates type=scatter fill=tozeroy when type=area', () => { - const onUpdate = jest.fn(); + const onUpdateTraces = jest.fn(); const editorProps = { ...fixtures.scatter({data: [{type: 'scatter', mode: 'markers'}]}), - onUpdate, + onUpdateTraces, plotly, }; const wrapper = mount( @@ -89,7 +89,7 @@ describe('TraceSelector', () => { const innerDropdown = wrapper.find(Dropdown); innerDropdown.prop('onChange')('area'); - const {payload} = onUpdate.mock.calls[0][0]; + const payload = onUpdateTraces.mock.calls[0][0]; expect(payload.update).toEqual({fill: 'tozeroy', type: 'scatter'}); }); }); diff --git a/src/hub.js b/src/hub.js deleted file mode 100644 index 2bbdf2f00..000000000 --- a/src/hub.js +++ /dev/null @@ -1,165 +0,0 @@ -import nestedProperty from 'plotly.js/src/lib/nested_property'; -import {EDITOR_ACTIONS} from './lib/constants'; -import isNumeric from 'fast-isnumeric'; - -/* eslint-disable no-console */ -const log = console.log.bind(console); -/* eslint-enable no-console */ - -// Revisions: An optional system to prevent unnecessary rerenders. -// There are two revision counters. One for the react-plotly.js -// component and one for react-plotly.js-editor. They are uncoupled because -// the plot can update the data internally (for example if a user updates the -// title in the plot directly). This should trigger a editorSourcesUpdated counter -// but not a figureUpdated counter since in this case the plot has already updated -// and only the editor needs updating. Similarly there is the case where dataSources -// may update but the Plotly.react component may determine that nothing needs to -// be rerendered. In this case the Editor may not need to update. -// If the revisions are not passed to react-plotly.js or react-plotly.js-editor -// they will always update. -export default class PlotlyHub { - constructor(config = {}) { - this.config = config; - this.editorRevision = config.editorRevision || 0; - this.plotRevision = config.plotRevision || 0; - - this.figureUpdated = this.figureUpdated.bind(this); - this.editorSourcesUpdated = this.editorSourcesUpdated.bind(this); - this.handlePlotUpdate = this.handlePlotUpdate.bind(this); - this.handlePlotInitialized = this.handlePlotInitialized.bind(this); - this.handleEditorUpdate = this.handleEditorUpdate.bind(this); - } - - // - // @method figureUpdates - // - // The underlying figure has changed. Calling this function - // will alert the Plotly.js component that it must redraw. - figureUpdated() { - this.plotRevision++; - const {plotRevision, editorRevision} = this; - if (this.config.debug) { - log('hub.figureUpdated', {plotRevision, editorRevision}); - } - if (this.config.onUpdate) { - this.config.onUpdate({ - plotRevision, - editorRevision, - graphDiv: this.graphDiv, - }); - } - } - - // - // @method editorSourcesUpdated - // - // The plot has rendered or other editor data such as dataSourceOptions - // has changed. Calling this function will alert the Editor that it must redraw. - editorSourcesUpdated() { - this.editorRevision++; - const {plotRevision, editorRevision} = this; - if (this.config.debug) { - log('hub.editorSourcesUpdated', {plotRevision, editorRevision}); - } - if (this.config.onUpdate) { - this.config.onUpdate({ - plotRevision, - editorRevision, - graphDiv: this.graphDiv, - }); - } - } - - // - // @method handlePlotUpdate - // - // Triggers editor UI update when the plot has been modified, - // whether by a restyle, a redraw, or by some other interaction. - // - // @param {object} graphDiv - graph div - // - handlePlotUpdate(graphDiv) { - if (this.config.debug) { - log('hub.handlePlotUpdate'); - } - this.graphDiv = graphDiv; - this.editorSourcesUpdated(); - } - - // - // @method handlePlotUpdate - // - // Triggers editor UI update when the plot has been modified, - // whether by a restyle, a - // redraw, or by some other interaction. - // - // @param {object} graphDiv - graph div - // - handlePlotInitialized(graphDiv) { - if (this.config.debug) { - log('hub.handlePlotInitialized'); - } - this.graphDiv = graphDiv; - this.editorSourcesUpdated(); - } - - // - // @method handleEditorUpdate - // - handleEditorUpdate({graphDiv, type, payload}) { - if (this.config.debug) { - log('hub.handleEditorUpdate', {type, payload}); - } - - this.graphDiv = graphDiv; - - switch (type) { - case EDITOR_ACTIONS.UPDATE_TRACES: - for (let i = 0; i < payload.traceIndexes.length; i++) { - for (const attr in payload.update) { - const traceIndex = payload.traceIndexes[i]; - const prop = nestedProperty(graphDiv.data[traceIndex], attr); - const value = payload.update[attr]; - if (value !== void 0) { - prop.set(value); - } - } - } - this.figureUpdated(); - break; - - case EDITOR_ACTIONS.UPDATE_LAYOUT: - for (const attr in payload.update) { - const prop = nestedProperty(graphDiv.layout, attr); - const value = payload.update[attr]; - if (value !== void 0) { - prop.set(value); - } - } - this.figureUpdated(); - break; - - case EDITOR_ACTIONS.ADD_TRACE: - graphDiv.data.push({x: [], y: []}); - this.figureUpdated(); - break; - - case EDITOR_ACTIONS.DELETE_TRACE: - if (payload.traceIndexes && payload.traceIndexes.length) { - graphDiv.data.splice(payload[0], 1); - this.figureUpdated(); - } - break; - - case EDITOR_ACTIONS.DELETE_ANNOTATION: - if (isNumeric(payload.annotationIndex)) { - graphDiv.layout.annotations.splice(payload.annotationIndex, 1); - this.figureUpdated(); - } - break; - - default: - throw new Error('must specify an action type to handleEditorUpdate'); - } - } -} diff --git a/src/index.js b/src/index.js index a7cdbf152..84b314887 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,3 @@ -import Hub from './hub'; import PlotlyEditor from './PlotlyEditor'; import { connectAnnotationToLayout, @@ -59,7 +58,6 @@ export { Flaglist, Fold, FontSelector, - Hub, Info, Layout, LayoutNumericFraction, diff --git a/src/lib/__tests__/connectAnnotationToLayout-test.js b/src/lib/__tests__/connectAnnotationToLayout-test.js index 5b204b43a..1cf546f9a 100644 --- a/src/lib/__tests__/connectAnnotationToLayout-test.js +++ b/src/lib/__tests__/connectAnnotationToLayout-test.js @@ -1,13 +1,12 @@ import NumericInput from '../../components/widgets/NumericInput'; import React from 'react'; -import {EDITOR_ACTIONS} from '../constants'; import {Numeric} from '../../components/fields'; import {TestEditor, fixtures, mount} from '../test-utils'; import {connectLayoutToPlot, connectAnnotationToLayout} from '..'; describe('connectAnnotationToLayout', () => { it('sends update to layout.annotation[index]', () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const fixture = fixtures.scatter({ layout: {annotations: [{text: 'hodor'}]}, }); @@ -16,7 +15,7 @@ describe('connectAnnotationToLayout', () => { ); mount( - + ) @@ -24,9 +23,7 @@ describe('connectAnnotationToLayout', () => { .find('.js-numeric-increase') .simulate('click'); - const update = onUpdate.mock.calls[0][0]; - const {type, payload} = update; - expect(type).toBe(EDITOR_ACTIONS.UPDATE_LAYOUT); + const payload = onUpdateLayout.mock.calls[0][0]; expect(payload.update).toEqual({'annotations[0].textangle': 1}); }); }); diff --git a/src/lib/__tests__/connectLayoutToPlot-test.js b/src/lib/__tests__/connectLayoutToPlot-test.js index 297922340..0403743e3 100644 --- a/src/lib/__tests__/connectLayoutToPlot-test.js +++ b/src/lib/__tests__/connectLayoutToPlot-test.js @@ -1,7 +1,6 @@ import NumericInput from '../../components/widgets/NumericInput'; import React from 'react'; import connectLayoutToPlot from '../connectLayoutToPlot'; -import {EDITOR_ACTIONS} from '../constants'; import {Fold, Panel, Section} from '../../components/containers'; import {Numeric} from '../../components/fields'; import {TestEditor, fixtures, plotly} from '../test-utils'; @@ -29,10 +28,10 @@ Layouts.forEach(Layout => { }); it(`sends updates to gd._layout`, () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const wrapper = mount( @@ -45,9 +44,8 @@ Layouts.forEach(Layout => { const widthUpdate = 200; wrapper.prop('onChange')(widthUpdate); - const event = onUpdate.mock.calls[0][0]; - expect(event.type).toBe(EDITOR_ACTIONS.UPDATE_LAYOUT); - expect(event.payload).toEqual({update: {width: widthUpdate}}); + const payload = onUpdateLayout.mock.calls[0][0]; + expect(payload).toEqual({update: {width: widthUpdate}}); }); it(`automatically computes min and max defaults`, () => { diff --git a/src/lib/__tests__/connectTraceToPlot-test.js b/src/lib/__tests__/connectTraceToPlot-test.js index 5c36074b1..b8a9f0633 100644 --- a/src/lib/__tests__/connectTraceToPlot-test.js +++ b/src/lib/__tests__/connectTraceToPlot-test.js @@ -1,7 +1,6 @@ import NumericInput from '../../components/widgets/NumericInput'; import React from 'react'; import connectTraceToPlot from '../connectTraceToPlot'; -import {EDITOR_ACTIONS} from '../constants'; import {Fold, Panel, Section} from '../../components/containers'; import {Numeric} from '../../components/fields'; import {TestEditor, fixtures, plotly} from '../test-utils'; @@ -31,9 +30,9 @@ Traces.forEach(Trace => { }); it('sends updates to gd.data', () => { - const onUpdate = jest.fn(); + const onUpdateTraces = jest.fn(); const wrapper = mount( - + @@ -44,18 +43,16 @@ Traces.forEach(Trace => { const sizeUpdate = 200; wrapper.prop('onChange')(sizeUpdate); - const event = onUpdate.mock.calls[0][0]; - expect(event.type).toBe(EDITOR_ACTIONS.UPDATE_TRACES); - expect(event.payload).toEqual({ + const payload = onUpdateTraces.mock.calls[0][0]; + expect(payload).toEqual({ update: {'marker.size': sizeUpdate}, traceIndexes: [0], }); }); it('automatically computes min and max defaults', () => { - const onUpdate = jest.fn(); const wrapper = mount( - + diff --git a/src/lib/__tests__/multiValued-test.js b/src/lib/__tests__/multiValued-test.js index 21294b463..b9b128ec0 100644 --- a/src/lib/__tests__/multiValued-test.js +++ b/src/lib/__tests__/multiValued-test.js @@ -36,7 +36,7 @@ describe('multiValued Numeric', () => { }); it('uses multiValued defaultContainer as default increment value', () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const xaxisLowerRange = -30; const fixtureProps = fixtures.scatter({ layout: {xaxis: {range: [xaxisLowerRange, 3]}, yaxis: {range: [0, 3]}}, @@ -44,16 +44,15 @@ describe('multiValued Numeric', () => { const AxesNumeric = connectLayoutToPlot(connectAxesToLayout(Numeric)); mount( - + ) .find('.js-numeric-increase') .simulate('click'); - expect(onUpdate).toBeCalled(); - const update = onUpdate.mock.calls[0][0]; - const {payload} = update; + expect(onUpdateLayout).toBeCalled(); + const payload = onUpdateLayout.mock.calls[0][0]; expect(payload.update).toEqual({ 'xaxis.range[0]': xaxisLowerRange, 'yaxis.range[0]': xaxisLowerRange, diff --git a/src/lib/__tests__/nestedContainerConnections-test.js b/src/lib/__tests__/nestedContainerConnections-test.js index 709f4abcd..f7e47a04d 100644 --- a/src/lib/__tests__/nestedContainerConnections-test.js +++ b/src/lib/__tests__/nestedContainerConnections-test.js @@ -1,6 +1,5 @@ import NumericInput from '../../components/widgets/NumericInput'; import React from 'react'; -import {EDITOR_ACTIONS} from '../constants'; import {Numeric, Section, Panel} from '../../components'; import {TestEditor, fixtures, mount} from '../test-utils'; import { @@ -14,13 +13,13 @@ import { describe('Plot Connection', () => { it('can connect Field directly with full connection pipeline', () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const fixtureProps = fixtures.scatter({layout: {xaxis: {range: [0, 10]}}}); const LayoutAxesNumeric = connectLayoutToPlot( connectAxesToLayout(connectToContainer(Numeric)) ); mount( - + ) @@ -29,21 +28,19 @@ describe('Plot Connection', () => { .find('.js-numeric-increase') .simulate('click'); - expect(onUpdate).toBeCalled(); - const update = onUpdate.mock.calls[0][0]; - const {type, payload} = update; - expect(type).toBe(EDITOR_ACTIONS.UPDATE_LAYOUT); + expect(onUpdateLayout).toBeCalled(); + const payload = onUpdateLayout.mock.calls[0][0]; expect(payload).toEqual({update: {'xaxis.range[0]': 1}}); }); it('can connect to layout when connected within trace context', () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const fixtureProps = fixtures.scatter({layout: {width: 10}}); const TraceLayoutNumeric = connectTraceToPlot( connectLayoutToPlot(connectToContainer(Numeric)) ); mount( - + ) @@ -52,16 +49,13 @@ describe('Plot Connection', () => { .find('.js-numeric-increase') .simulate('click'); - expect(onUpdate).toBeCalled(); - const update = onUpdate.mock.calls[0][0]; - const {type, payload} = update; - expect(type).toBe(EDITOR_ACTIONS.UPDATE_LAYOUT); + expect(onUpdateLayout).toBeCalled(); + const payload = onUpdateLayout.mock.calls[0][0]; expect(payload).toEqual({update: {width: 11}}); }); // see https://github.com/plotly/react-plotly.js-editor/issues/58#issuecomment-345492794 it("can't find correct Container when Section divides Trace and Layout", () => { - const onUpdate = jest.fn(); const fixtureProps = fixtures.scatter({layout: {width: 10}}); const DeeplyConnectedNumeric = connectTraceToPlot( connectLayoutToPlot( @@ -74,7 +68,7 @@ describe('Plot Connection', () => { ); const wrapper = mount( - +
@@ -90,7 +84,7 @@ describe('Plot Connection', () => { }); it('can supplyPlotProps within
and nested Layout and Trace', () => { - const onUpdate = jest.fn(); + const onUpdateLayout = jest.fn(); const fixtureProps = fixtures.scatter({layout: {width: 10}}); const TracePanel = connectTraceToPlot(Panel); const LayoutConnectedNumeric = connectLayoutToPlot( @@ -105,7 +99,7 @@ describe('Plot Connection', () => { ); mount( - +
@@ -118,10 +112,8 @@ describe('Plot Connection', () => { .find('.js-numeric-increase') .simulate('click'); - expect(onUpdate).toBeCalled(); - const update = onUpdate.mock.calls[0][0]; - const {type, payload} = update; - expect(type).toBe(EDITOR_ACTIONS.UPDATE_LAYOUT); + expect(onUpdateLayout).toBeCalled(); + const payload = onUpdateLayout.mock.calls[0][0]; expect(payload).toEqual({update: {width: 11}}); }); From dceb16432794babe90ff474798e667a1e938c2cb Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 19 Dec 2017 15:34:53 -0500 Subject: [PATCH 6/7] explanatory comments --- examples/async-data/src/App.js | 5 ++++- examples/simple/src/App.js | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/async-data/src/App.js b/examples/async-data/src/App.js index 61359b4ef..9f1089a33 100644 --- a/examples/async-data/src/App.js +++ b/examples/async-data/src/App.js @@ -45,6 +45,7 @@ class Backend { ee(Backend.prototype); const backend = new Backend({ + // eslint-disable-next-line no-magic-numbers dataSources: {col1: [1, 2, 3], col2: [4, 3, 2], col3: [17, 13, 9]}, delay: 10, }); @@ -53,7 +54,9 @@ class App extends Component { constructor() { super(); - // A basic starting plotly.js figure object. Instead of assigning + // This object is passed to Plotly.js, which then causes it to be + // overwritten with a full DOM node that contains data, layout, _fullData, + // _fullLayout etc in handlePlotUpdate() const graphDiv = { data: [{type: 'scatter'}], layout: {title: 'Room readings'}, diff --git a/examples/simple/src/App.js b/examples/simple/src/App.js index 17cb855f1..0e2789476 100644 --- a/examples/simple/src/App.js +++ b/examples/simple/src/App.js @@ -12,6 +12,7 @@ class App extends Component { super(); // DataSources are a mapping of column or data source ids to data arrays. + // eslint-disable-next-line no-magic-numbers const dataSources = {col1: [1, 2, 3], col2: [4, 3, 2], col3: [17, 13, 9]}; const dataSourceOptions = [ {value: 'col1', label: 'CO2'}, @@ -19,7 +20,9 @@ class App extends Component { {value: 'col3', label: 'SiO2'}, ]; - // A basic starting plotly.js figure object. Instead of assigning + // This object is passed to Plotly.js, which then causes it to be + // overwritten with a full DOM node that contains data, layout, _fullData, + // _fullLayout etc in handlePlotUpdate() const graphDiv = { data: [{type: 'scatter', xsrc: 'col1', ysrc: 'col2'}], layout: {title: 'Room readings'}, From 90a60f343e44b824279675854ee2c7b7e9b837f2 Mon Sep 17 00:00:00 2001 From: Nicolas Kruchten Date: Tue, 19 Dec 2017 15:43:24 -0500 Subject: [PATCH 7/7] PR feedback --- examples/async-data/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/async-data/package.json b/examples/async-data/package.json index fb6c06f6d..1b0a0a77b 100644 --- a/examples/async-data/package.json +++ b/examples/async-data/package.json @@ -8,6 +8,7 @@ "react": "^15.6.1", "react-dom": "^15.6.1", "react-plotly.js": "^1.0.4", + "react-plotly.js-editor": "^0.1.0", "react-scripts": "1.0.17" }, "scripts": {