Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change default export to combined EditorControls + Plot component #384

Merged
merged 9 commits into from
Mar 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 3 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Check out the demo of the latest release of the `DefaultEditor` at https://plotl
```
git clone [this repo]
cd react-chart-editor
cd examples/simple
cd examples/demo
npm install
npm start
```
Expand All @@ -28,55 +28,7 @@ See more examples

## Overview

This module's entry point is a React component called `<PlotlyEditor />` which connects to a [plotly.js](https://plot.ly/javascript/)-powered `<Plot />` component care of [`react-plotly.js`](https://github.com/plotly/react-plotly.js). A plotly.js plot is defined by a JSON-serializable object called a _figure_. `<PlotlyEditor />` accepts as children React components whose descendents are input elements wrapped via `connectToContainer()` calls so as to bind them to the `<Plot />`'s figure's values. If no children are passed to the `<PlotlyEditor />`, the `<DefaultEditor />` is used. This module also exposes the [building block components](#Built-in-Components) that comprise the `<DefaultEditor />` so that developers can create their own customized editors.

## Connecting `<PlotlyEditor />` to `<Plot />`

The binding between `<PlotlyEditor />` and `<Plot />` works a little differently that in most React apps because plotly.js mutates its properties. This is mapped onto React's one-way dataflow model via event handlers and shared revision numbers which trigger re-renders of mutated state. The following subset of the [simple example](https://github.com/plotly/react-chart-editor/tree/master/examples/simple) shows how this works using a parent component to store state, but the principle is the same with a different state-manage approach, as shown in the [redux example](https://github.com/plotly/react-chart-editor/tree/master/examples/redux):

```javascript
import PlotlyEditor from 'react-chart-editor';
import Plot from 'react-plotly.js';

class App extends Component {
constructor() {
super();
this.state = {graphDiv: {}, editorRevision: 0, plotRevision: 0};
}

handlePlotUpdate(graphDiv) {
this.setState(({editorRevision: x}) => ({editorRevision: x + 1, graphDiv}));
}

handleEditorUpdate() {
this.setState(({plotRevision: x}) => ({plotRevision: x + 1}));
}

render() {
return (
<div>
<PlotlyEditor
graphDiv={this.state.graphDiv}
onUpdate={this.handleEditorUpdate.bind(this)}
revision={this.state.editorRevision}
{...snip}
/>
<Plot
data={this.state.graphDiv.data}
layout={this.state.graphDiv.layout}
onUpdate={this.handlePlotUpdate.bind(this)}
revision={this.state.plotRevision}
{...snip}
/>
</div>
);
}
}
```

## Data Management

`<PlotlyEditor />` accepts a `dataSources` property which is an object of arrays of data, as well as a `dataSourceOptions` property which contains metadata about the `dataSources`, such as human-readable labels used to populate input elements like dropdown menus. `<PlotlyEditor />` treats these properties as immutable so any changes to them will trigger a rerender, and accepts an `onUpdateTraces` event handler property which is called whenever it needs to access a column from `dataSources`, enabling asynchronous data loading e.g. from remote APIs. The [async-data example](https://github.com/plotly/react-chart-editor/tree/master/examples/async-data) shows how this is done using a dummy asynchronous back-end proxy.
This module's entry point is a React component called `<PlotlyEditor />` which connects an instance of `<EditorControls />` to a [plotly.js](https://plot.ly/javascript/)-powered `<Plot />` component care of [`react-plotly.js`](https://github.com/plotly/react-plotly.js). `<PlotlyEditor />` accepts as children React components whose descendents are input elements wrapped via `connectToContainer()` calls so as to bind them to the `<Plot />`'s figure's values. If no children are passed to the `<PlotlyEditor />`, the `<DefaultEditor />` is used. This module also exposes the [building block components](#Built-in-Components) that comprise the `<DefaultEditor />` so that developers can create their own customized editors.

## Styling the `<DefaultEditor />` and the built-in components

Expand Down Expand Up @@ -198,7 +150,7 @@ For use in containers bound to annotations e.g. as children of `<AnnotationAccor

To use Satellite Maps in the Editor, [Mapbox access tokens](https://www.mapbox.com/help/how-access-tokens-work/) are required.

Once you have your tokens, you can provide it as a config prop to the `react-plotly.js` generated `Plot` component: `<Plot config={{mapboxAccessToken: 'your token'}}/>`
Once you have your tokens, you can provide it as a config prop to the `<PlotlyEditor />` component: `<PlotlyEditor config={{mapboxAccessToken: 'your token'}}/>`

## See also

Expand Down
71 changes: 21 additions & 50 deletions dev/App.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, {Component} from 'react';
import {hot} from 'react-hot-loader';
import plotly from 'plotly.js/dist/plotly';
import createPlotComponent from 'react-plotly.js/factory';
import PlotlyEditor from '../src';
import '../src/styles/main.scss';
import Nav from './Nav';
import PlotlyEditor from '../src';

// https://github.com/plotly/react-chart-editor#mapbox-access-tokens
import ACCESS_TOKENS from '../accessTokens';
Expand All @@ -22,18 +21,15 @@ const dataSourceOptions = Object.keys(dataSources).map(name => ({
label: name,
}));

const Plot = createPlotComponent(plotly);
const config = {mapboxAccessToken: ACCESS_TOKENS.MAPBOX, editable: true};

class App extends Component {
constructor() {
super();

// The graphDiv 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()
this.state = {
graphDiv: {},
plotRevision: 0,
data: [],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note the new state in App: just data and layout!

layout: {},
currentMockIndex: -1,
mocks: [],
};
Expand All @@ -49,61 +45,36 @@ class App extends Component {
.then(mocks => this.setState({mocks}));
}

handlePlotUpdate(graphDiv) {
this.setState({graphDiv});
}

handleEditorUpdate() {
this.setState(({plotRevision: x}) => ({plotRevision: x + 1}));
}

loadMock(mockIndex) {
const mock = this.state.mocks[mockIndex];
fetch(mock.url, {
headers: new Headers({Accept: 'application/vnd.github.v3.raw'}),
})
.then(response => response.json())
.then(figure => {
const graphDiv = this.state.graphDiv;
graphDiv.layout = figure.layout;
graphDiv.data = figure.data;
this.setState(({plotRevision: x}) => ({
this.setState({
currentMockIndex: mockIndex,
plotRevision: x + 1,
}));
data: figure.data,
layout: figure.layout,
});
});
}

render() {
return (
<div className="app__container plotly-editor--theme-provider">
<div className="app">
<PlotlyEditor
graphDiv={this.state.graphDiv}
onUpdate={this.handleEditorUpdate.bind(this)}
dataSources={dataSources}
dataSourceOptions={dataSourceOptions}
plotly={plotly}
advancedTraceTypeSelector
/>
<div className="app__main" style={{width: '100%', height: '100%'}}>
<Plot
config={{mapboxAccessToken: ACCESS_TOKENS.MAPBOX, editable: true}}
data={this.state.graphDiv.data}
debug
layout={this.state.graphDiv.layout}
onInitialized={this.handlePlotUpdate.bind(this)}
onUpdate={this.handlePlotUpdate.bind(this)}
revision={this.state.plotRevision}
useResizeHandler
style={{
width: '100%',
height: '100%',
minHeight: 'calc(100vh - 50px)',
}}
/>
</div>
</div>
<div className="app">
<PlotlyEditor
data={this.state.data}
layout={this.state.layout}
config={config}
dataSources={dataSources}
dataSourceOptions={dataSourceOptions}
plotly={plotly}
onUpdate={(data, layout) => this.setState({data, layout})}
useResizeHandler
debug
advancedTraceTypeSelector
/>
<Nav
currentMockIndex={this.state.currentMockIndex}
loadMock={this.loadMock}
Expand Down
38 changes: 9 additions & 29 deletions dev/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,29 @@ body {
font-family: sans-serif;
}

.app__container {
display: flex;
flex-direction: column;
}

.app {
display: flex;
/*
We are defining the max height of the app so that the editor knows how big to be
currently the editor will take up whatever space it can if it is not constrained in its parent
*/
flex-grow: 1;
height: calc(100vh - 50px);
max-height: calc(100vh - 50px);
width: 100%;
}

.app__main {
max-width: 100%;
height: calc(100vh - 50px);
max-height: calc(100vh - 50px);
overflow: auto;
flex-grow: 1;
}

.mock-nav {
height: 50px;
width: 100%;
background-color: var(--color-background-inverse);
background-color: #506784;
display: inline-flex;
color: white;
}

.mock-nav__label{
line-height: 50px;
padding-left: 10px;
.mock-nav__label {
line-height: 50px;
padding-left: 10px;
}

.mock-nav__select{
width: 300px;
margin-left: 20px;
margin-right: 20px;
margin-top: 7px;
.mock-nav__select {
width: 300px;
margin-left: 20px;
margin-right: 20px;
margin-top: 7px;
}

.Select.open-top .Select-menu-outer {
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# `react-chart-editor` examples

* [Simple `react-chart-editor` example](simple): `DefaultEditor`, synchronous data, top-level component state management
* [Async-data `react-chart-editor` example](async-data): `DefaultEditor`, asynchronous data, top-level component state management
* [Demo `react-chart-editor` example](demo): `DefaultEditor`, top-level component state management, navbar to load mocks (same as dev app but no hot-reloading)
* [Custom `react-chart-editor` example](custom): `CustomEditor`, synchronous data, top-level component state management
* [Redux `react-chart-editor` example](redux): `DefaultEditor`, synchronous data, Redux state management
58 changes: 18 additions & 40 deletions examples/custom/src/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, {Component} from 'react';
import plotly from 'plotly.js/dist/plotly';
import createPlotComponent from 'react-plotly.js/factory';
import PlotlyEditor from 'react-chart-editor';
import CustomEditor from './CustomEditor';
import 'react-chart-editor/lib/react-chart-editor.css';
Expand All @@ -15,62 +14,41 @@ const dataSourceOptions = Object.keys(dataSources).map(name => ({
label: name,
}));

const Plot = createPlotComponent(plotly);
const config = {editable: true};

class App extends Component {
constructor() {
super();

// The graphDiv 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()
this.state = {
graphDiv: {
data: [
{
type: 'scatter',
x: dataSources.col1,
y: dataSources.col2,
marker: {color: dataSources.col3},
},
],
},
plotRevision: 0,
data: [
{
type: 'scatter',
x: dataSources.col1,
y: dataSources.col2,
marker: {color: dataSources.col3},
},
],
layout: {},
};
}

handlePlotUpdate(graphDiv) {
this.setState({graphDiv});
}

handleEditorUpdate() {
this.setState(({plotRevision: x}) => ({plotRevision: x + 1}));
}

render() {
return (
<div className="app">
<PlotlyEditor
locale="en"
graphDiv={this.state.graphDiv}
onUpdate={this.handleEditorUpdate.bind(this)}
plotly={plotly}
data={this.state.data}
layout={this.state.layout}
config={config}
dataSources={dataSources}
dataSourceOptions={dataSourceOptions}
plotly={plotly}
onUpdate={(data, layout) => this.setState({data, layout})}
useResizeHandler
debug
advancedTraceTypeSelector
>
<CustomEditor />
</PlotlyEditor>
<div className="app__main">
<Plot
debug
data={this.state.graphDiv.data}
layout={this.state.graphDiv.layout}
config={{editable: true}}
onUpdate={this.handlePlotUpdate.bind(this)}
onInitialized={this.handlePlotUpdate.bind(this)}
revision={this.state.plotRevision}
/>
</div>
</div>
);
}
Expand Down
16 changes: 2 additions & 14 deletions examples/custom/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,7 @@ body {
font-family: sans-serif;
}

.app{
display: flex;
/*
We are defining the max height of the app so that the editor knows how big to be
currently the editor will take up whatever space it can if it is not constrained in its parent
*/
min-height: 100vh;
.app {
height: 100vh;
max-height: 100vh;
width: 100%;
}
.app__main {
max-width: 100%;
max-height: 100vh;
overflow: auto;
flex-grow: 1;
}
Loading