diff --git a/README.md b/README.md index 9795e8c..4233932 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,14 @@ * [Installation](#installation) * [Quick start](#quick-start) +* [State management](#state-management) +* [Refreshing the Plot](#refreshing-the-plot) * [Customizing the `plotly.js` bundle](#customizing-the-plotlyjs-bundle) -* [Loading from a ` - -``` +Furthermore, when called, `Plotly.react` will only refresh the data being plotted if the _identity_ of the data arrays (e.g. `x`, `y`, `color` etc) has changed, or if `layout.datarevision` has changed. -And instantiate the component with +In short, this means that simply adding data points to a trace in `data` or changing a value in `layout` will not cause a plot to update unless this is done immutably via something like [immutability-helper](https://github.com/kolodny/immutability-helper) if performance considerations permit it, or unless `revision` and/or `layout.datarevision` are used to force a rerender. -```javascript -const Plot = createPlotlyComponent(Plotly); +## API Reference -ReactDOM.render( - React.createElement(Plot, { - data: [{x: [1, 2, 3], y: [2, 1, 3]}], - }), - document.getElementById('root') -); -``` +### Basic Props -You can see an example of this method in action -[here](https://codepen.io/rsreusser/pen/qPgwwJ?editors=1010). +**Warning**: for the time being, this component may _mutate_ its `layout` and `data` props in response to user input, going against React rules. This behaviour will change in the near future once https://github.com/plotly/plotly.js/issues/2389 is completed. -## API +| Prop | Type | Default | Description | +| ------------------ | ---------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `data` | `Array` | `[]` | list of trace objects (see https://plot.ly/javascript/reference/) | +| `layout` | `Object` | `undefined` | layout object (see https://plot.ly/javascript/reference/#layout) | +| `frames` | `Array` | `undefined` | list of frame objects (see https://plot.ly/javascript/reference/) | +| `config` | `Object` | `undefined` | config object (see https://plot.ly/javascript/configuration-options/) | +| `revision` | `Number` | `undefined` | When provided, causes the plot to update _only_ when the revision is incremented. | +| `onInitialized` | `Function(figure, graphDiv)` | `undefined` | Callback executed after plot is initialized. See below for parameter information. | +| `onUpdate` | `Function(figure, graphDiv)` | `undefined` | Callback executed when when a plot is updated due to new data or layout, or when user interacts with a plot. See below for parameter information. | +| `onPurge` | `Function(figure, graphDiv)` | `undefined` | Callback executed when component unmounts, before `Plotly.purge` strips the `graphDiv` of all private attributes. See below for parameter information. | +| `onError` | `Function(err)` | `undefined` | Callback executed when a plotly.js API method rejects | +| `divId` | `string` | `undefined` | id assigned to the `
` into which the plot is rendered. | +| `className` | `string` | `undefined` | applied to the `
` into which the plot is rendered | +| `style` | `Object` | `{position: 'relative', display: 'inline-block'}` | used to style the `
` into which the plot is rendered | +| `debug` | `Boolean` | `false` | Assign the graph div to `window.gd` for debugging | +| `useResizeHandler` | `Boolean` | `false` | When true, adds a call to `Plotly.Plot.resize()` as a `window.resize` event handler | +| `fit` | `Boolean` | `false` | _deprecated_ When true, will set `layout.height` and `layout.width` to the component's parent's size if they are unspecified, and will update them on `window.resize` | -### Props +**Note**: To make a plot responsive, i.e. to fill its containing element and resize when the window is resized, use `style` or `className` to set the dimensions of the element (i.e. using `width: 100%; height: 100%` or some similar values) and set `useResizeHandler` to `true` while setting `layout.autosize` to `true` and leaving `layout.height` and `layout.width` undefined. This will implement the behaviour documented here: https://plot.ly/javascript/responsive-fluid-layout/ -**Note**: This component will not refresh the plot unless either the `revision` prop is defined and has changed, OR unless a shallow equality check on `data`, `layout`, `frames` and `config` fails. +#### Callback signature: `Function(figure, graphDiv)` -**Note**: for the time being, this component may mutate its `layout` and `data` props in response to user input, going against React rules. This behaviour will change in the near future once https://github.com/plotly/plotly.js/issues/2389 is completed. +The `onInitialized`, `onUpdate` and `onPurge` props are all functions which will be called with two arguments: `figure` and `graphDiv`. -| Prop | Type | Default | Description | -| ------------------ | ---------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `data` | `Array` | `[]` | list of trace objects | -| `layout` | `Object` | `undefined` | layout object | -| `config` | `Object` | `undefined` | config object | -| `style` | `Object` | `{position: 'relative', display: 'inline-block'}` | used to style the `
` into which the plot is rendered | -| `divId` | `string` | `undefined` | id assigned to the `
` into which the plot is rendered. | -| `className` | `string` | `undefined` | applied to the `
` into which the plot is rendered | -| `frames` | `Array` | `undefined` | list of frame objects | -| `useResizeHandler` | `Boolean` | `false` | When true, adds a call to `Plotly.Plot.resize()` as a `window.resize` event handler | -| `revision` | `Number` | `undefined` | When provided, causes the plot to update _only_ when the revision is incremented. | -| `debug` | `Boolean` | `false` | Assign the graph div to `window.gd` for debugging | -| `onInitialized` | `Function` | `undefined` | Callback executed after plot is initialized. This function recieves a reference to the `
` DOM node as an argument, from which `data`, `layout`, `config`, and `frames` can be extracted and persisted. | -| `onPurge` | `Function` | `undefined` | Callback executed when component unmounts. Unmounting triggers a Plotly.purge event which strips the graphDiv of all plotly.js related information including data and layout. This hook gives application writers a chance to pull data and layout off the DOM. | -| `onUpdate` | `Function` | `undefined` | Callback executed when a plotly.js API method resolves. This function recieves a reference to the `
` DOM node as an argument, from which `data`, `layout`, `config`, and `frames` can be extracted and persisted. | -| `onError` | `Function` | `undefined` | Callback executed when a plotly.js API method rejects | -| `fit` | `Boolean` | `false` | _deprecated_ When true, will set `layout.height` and `layout.width` to the component's parent's size if they are unspecified, and will update them on `window.resize` | - -To make a plot responsive, i.e. to fill its containing element and resize when the window is resized, use `style` or `className` to set the dimensions of the element (i.e. using `width: 100%; height: 100%` or some similar values) and set `useResizeHandler` to `true` while setting `layout.autosize` to `true` and leaving `layout.height` and `layout.width` undefined. This will implement the behaviour documented here: https://plot.ly/javascript/responsive-fluid-layout/ +* `figure` is a serializable object with three keys corresponding to input props: `data`, `layout` and `frames`. + * As mentioned above, for the time being, this component may _mutate_ its `layout` and `data` props in response to user input, going against React rules. This behaviour will change in the near future once https://github.com/plotly/plotly.js/issues/2389 is completed. +* `graphDiv` is a reference to the (unserializable) DOM node into which the figure was rendered. ### Event handler props -Event handlers for -[`plotly.js` events](https://plot.ly/javascript/plotlyjs-events/) may be -attached through the following props. +Event handlers for specific [`plotly.js` events](https://plot.ly/javascript/plotlyjs-events/) may be attached through the following props: | Prop | Type | Plotly Event | | ------------------------- | ---------- | ------------------------------ | @@ -183,6 +178,48 @@ attached through the following props. | `onTransitionInterrupted` | `Function` | `plotly_transitioninterrupted` | | `onUnhover` | `Function` | `plotly_unhover` | +## Customizing the `plotly.js` bundle + +By default, the `Plot` component exported by this library loads a precompiled version of all of `plotly.js`, so `plotly.js` must be installed as a peer dependency. This bundle is around 6Mb unminified, and minifies to just over 2Mb. + +If you do not wish to use this version of `plotly.js`, e.g. if you want to use a [different precompiled bundle](https://github.com/plotly/plotly.js/blob/master/dist/README.md#partial-bundles) or if your wish to [assemble you own customized bundle](https://github.com/plotly/plotly.js#modules), or if you wish to load `plotly.js` [from a CDN](https://github.com/plotly/plotly.js#use-the-plotlyjs-cdn-hosted-by-fastly), you can skip the installation of as a peer dependency (and ignore the resulting warning) and use the `createPlotComponent` method to get a `Plot` component, instead of importing it: + +```javascript +// simplest method: uses precompiled complete bundle from `plotly.js` +import Plot from 'react-plotly.js'; + +// customizable method: use your own `Plotly` object +import createPlotlyComponent from 'react-plotly.js/factory'; +const Plot = createPlotlyComponent(Plotly); +``` + +## Loading from a ` + +``` + +And instantiate the component with + +```javascript +const Plot = createPlotlyComponent(Plotly); + +ReactDOM.render( + React.createElement(Plot, { + data: [{x: [1, 2, 3], y: [2, 1, 3]}], + }), + document.getElementById('root') +); +``` + +You can see an example of this method in action +[here](https://codepen.io/rsreusser/pen/qPgwwJ?editors=1010). + ## Development To get started: diff --git a/src/factory.js b/src/factory.js index ff4b786..c704f47 100644 --- a/src/factory.js +++ b/src/factory.js @@ -63,7 +63,7 @@ export default function plotComponentFactory(Plotly) { this.attachUpdateEvents = this.attachUpdateEvents.bind(this); this.getRef = this.getRef.bind(this); this.handleUpdate = this.handleUpdate.bind(this); - this.handleUpdateWithProps = this.handleUpdateWithProps.bind(this); + this.figureCallback = this.figureCallback.bind(this); } componentDidMount() { @@ -79,12 +79,10 @@ export default function plotComponentFactory(Plotly) { .then(() => this.syncWindowResize(null, false)) .then(this.syncEventHandlers) .then(this.attachUpdateEvents) - .then( - () => this.props.onInitialized && this.props.onInitialized(this.el) - ) - .catch(e => { - console.error('Error while plotting:', e); - return this.props.onError && this.props.onError(); + .then(() => this.figureCallback(this.props.onInitialized)) + .catch(err => { + console.error('Error while plotting:', err); + return this.props.onError && this.props.onError(err); }); } @@ -124,7 +122,7 @@ export default function plotComponentFactory(Plotly) { .then(() => { if (!hasReactAPIMethod) this.attachUpdateEvents(); }) - .then(() => this.handleUpdateWithProps(nextProps)) + .then(() => this.figureCallback(nextProps.onUpdate)) .catch(err => { console.error('Error while plotting:', err); this.props.onError && this.props.onError(err); @@ -132,9 +130,8 @@ export default function plotComponentFactory(Plotly) { } componentWillUnmount() { - if (this.props.onPurge) { - this.props.onPurge(this.el); - } + this.figureCallback(this.props.onPurge); + if (this.fitHandler && isBrowser) { window.removeEventListener('resize', this.fitHandler); this.fitHandler = null; @@ -166,12 +163,14 @@ export default function plotComponentFactory(Plotly) { } handleUpdate() { - this.handleUpdateWithProps(this.props); + this.figureCallback(this.props.onUpdate); } - handleUpdateWithProps(props) { - if (props.onUpdate && typeof props.onUpdate === 'function') { - props.onUpdate(this.el); + figureCallback(callback) { + if (typeof callback === 'function') { + const {data, layout, _transitionData: {_frames: frames}} = this.el; + const figure = {data, layout, frames}; // for extra clarity! + callback(figure, this.el); } }