UNPKG

@eeacms/react-chart-editor

Version:

plotly.js chart editor react component UI

383 lines (367 loc) 12.5 kB
import React, { Component } from 'react'; import { hot } from 'react-hot-loader/root'; import plotly from 'plotly.js/dist/plotly-with-meta'; import '../src/styles/main.scss'; import AceEditor from 'react-ace'; import Select from 'react-select'; import PlotlyEditor, { DefaultEditor, Panel } from '../src'; import Inspector from 'react-inspector'; // import dataSources from './dataSources'; // https://github.com/plotly/react-chart-editor#mapbox-access-tokens import ACCESS_TOKENS from '../accessTokens'; // import {customConfigTest} from './customConfigTest'; const config = { mapboxAccessToken: ACCESS_TOKENS.MAPBOX, editable: true }; // eslint-disable-next-line no-unused-vars const traceTypesConfig = { traces: (_) => [ { value: 'scatter', icon: 'scatter', label: _('Scatter'), }, { value: 'line', label: _('Line'), }, { value: 'area', label: _('Area'), }, { value: 'bar', label: _('Bar'), }, { value: 'histogram', label: _('Histogram'), }, { value: 'table', label: _('Table'), }, { value: 'pie', label: _('Pie'), }, { value: 'box', label: _('Box'), }, { value: 'histogram2d', label: _('Histogram 2D'), }, ], complex: true, }; // eslint-disable-next-line no-unused-vars const chartHelp = { scatter: { helpDoc: 'https://plotly.com/javascript/line-and-scatter/', feedQuery: true }, bar: { helpDoc: 'https://plotly.com/javascript/bar-charts/', feedQuery: true, }, line: { helpDoc: 'https://plotly.com/javascript/line-charts/', feedQuery: true, }, area: { helpDoc: 'https://plotly.com/javascript/filled-area-plots/', feedQuery: true, }, heatmap: { helpDoc: 'https://plotly.com/javascript/heatmaps/', feedQuery: true }, table: { helpDoc: 'https://plotly.com/javascript/table/', feedQuery: true }, contour: { helpDoc: 'https://plotly.com/javascript/contour-plots/', feedQuery: true }, pie: { helpDoc: 'https://plotly.com/javascript/pie-charts/', feedQuery: true }, box: { helpDoc: 'https://plotly.com/javascript/box-plots/', feedQuery: true }, histogram: { helpDoc: 'https://plotly.com/javascript/histograms/', feedQuery: true }, histogram2d: { helpDoc: 'https://plotly.com/javascript/2D-Histogram/', feedQuery: true }, histogram2dcontour: { helpDoc: 'https://plotly.com/javascript/2d-histogram-contour/', feedQuery: true, }, scatter3d: { helpDoc: 'https://plotly.com/javascript/3d-scatter-plots/', feedQuery: true }, line3d: { helpDoc: 'https://plotly.com/javascript/3d-line-plots/', feedQuery: true }, surface: { helpDoc: 'https://plotly.com/javascript/3d-surface-plots/', feedQuery: true }, mesh3d: { helpDoc: 'https://plotly.com/javascript/3d-mesh/', feedQuery: true }, cone: { helpDoc: 'https://plotly.com/javascript/cone-plot/', feedQuery: true }, streamtube: { helpDoc: 'https://plotly.com/javascript/streamtube-plot/', feedQuery: true }, scattermapbox: { helpDoc: 'https://plotly.com/javascript/scatter-tile-maps/', feedQuery: true, }, scattergeo: { helpDoc: 'https://plotly.com/javascript/scatter-plots-on-maps/', feedQuery: true, }, choroplethmapbox: { helpDoc: 'https://plotly.com/javascript/tile-county-choropleth/', feedQuery: true, }, choropleth: { helpDoc: 'https://plotly.com/javascript/choropleth-maps/', feedQuery: true }, densitymapbox: { helpDoc: 'https://plotly.com/javascript/tile-density-heatmaps/', feedQuery: true, }, candlestick: { helpDoc: 'https://plotly.com/javascript/candlestick-charts/', feedQuery: true }, ohlc: { helpDoc: 'https://plotly.com/javascript/ohlc-charts/', feedQuery: true }, waterfall: { helpDoc: 'https://plotly.com/javascript/waterfall-charts/', feedQuery: true }, funnel: { helpDoc: 'https://plotly.com/javascript/funnel-charts/', feedQuery: true }, funnelarea: { helpDoc: 'https://plotly.com/javascript/funnel-charts/#funnelarea-plot', feedQuery: true, }, scatterpolar: { helpDoc: 'https://plotly.com/javascript/polar-chart/', feedQuery: true }, barpolar: { helpDoc: '', feedQuery: true }, scatterternary: { helpDoc: 'https://plotly.com/javascript/ternary-plots/', feedQuery: true }, sunburst: { helpDoc: 'https://plotly.com/javascript/sunburst-charts/', feedQuery: true }, treemap: { helpDoc: 'https://plotly.com/javascript/treemaps/', feedQuery: true }, sankey: { helpDoc: 'https://plotly.com/javascript/sankey-diagram/', feedQuery: true }, }; class App extends Component { constructor() { super(); this.state = { forceRender: {}, data: [], layout: { margin: { t: 0, b: 0, l: 0, r: 0, }, xaxis: { automargin: true, }, yaxis: { automargin: true, }, // template: { // data: { // bar: [ // { // marker: { // color: '#FF0000', // }, // }, // { // marker: { // color: 'rgb(0,255,0)', // }, // }, // ], // }, // layout: { // margin: { // t: 0, // }, // font: { // size: 16, // color: 'rgb(255,0,0)', // }, // annotationdefaults: { // showarrow: false, // }, // imagedefaults: { // layer: 'below', // }, // colorscale: { // sequential: [ // [0, '#c8fff8'], // [0.17, '#a0e5dc'], // [0.33, '#78cac0'], // [0.5, '#50b0a4'], // [0.67, '#289588'], // [0.83, '#007b6c'], // [1, '#005248'], // ], // }, // yaxis: { // automargin: true, // }, // }, // }, }, frames: [], dataSources: { Day: ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'], 'Time of day': ['Morning', 'Afternoon', 'Evening'], 'Monday tasks': ['22', '12', '10'], 'Tuesday tasks': ['22', '32', '4'], 'Wednesday tasks': ['3', '44', '12'], 'Thursday tasks': ['3', '43', '22'], 'Friday tasks': ['13', '14', '3'], }, currentMockIndex: -1, mocks: [], }; this.loadMock = this.loadMock.bind(this); this.loadJSON = this.loadJSON.bind(this); this.updateState = this.updateState.bind(this); this.editor = React.createRef(); } UNSAFE_componentWillMount() { // curl https://api.github.com/repos/plotly/plotly.js/contents/test/image/mocks \ // | jq '[.[] | .name ]' > mocks.json fetch('/mocks.json') .then((response) => response.json()) .then((mocks) => this.setState({ mocks })); } loadMock(mockIndex) { const mockName = this.state.mocks[mockIndex]; const prefix = mockName[0] === '/' ? '' : 'https://api.github.com/repos/plotly/plotly.js/contents/test/image/mocks/'; fetch(prefix + mockName, { headers: new Headers({ Accept: 'application/vnd.github.v3.raw' }), }) .then((response) => response.json()) .then((figure) => { const { data, layout, frames } = figure; this.updateState(data, layout, frames, mockIndex); }); } updateState(data, layout, frames, currentMockIndex) { this.setState({ data, layout, frames, currentMockIndex, full: 'hit refresh', json_error: false, json_string: JSON.stringify({ data, layout, frames }, null, 2), }); } loadJSON() { try { const { data, layout, frames } = JSON.parse(this.state.json_string); this.updateState(data, layout, frames); } catch (e) { this.setState({ json_error: true }); } } render() { const dataSourceOptions = Object.keys(this.state.dataSources).map((name) => ({ value: name, label: name, })); return ( <div className="app"> <PlotlyEditor ref={this.editor} data={this.state.data} layout={this.state.layout} frames={this.state.frames} config={config} dataSources={this.state.dataSources} dataSourceOptions={dataSourceOptions} plotly={plotly} onUpdate={this.updateState} onUpdateDataSources={(dataSources) => this.setState({ dataSources })} forceRender={() => this.setState({ forceRender: {} })} divId="gd" useResizeHandler debug advancedTraceTypeSelector showFieldTooltips makeDefaultTrace={() => ({ type: 'bar', orientation: 'v' })} // glByDefault // traceTypesConfig={traceTypesConfig} // makeDefaultTrace={() => ({type: 'scattergl', mode: 'markers'})} // fontOptions={[{label:'Arial', value: 'arial'}]} chartHelp={chartHelp} // customConfig={customConfigTest} > <DefaultEditor // menuPanelOrder={[ // {group: 'Dev', name: 'JSON'}, // {group: 'Dev', name: 'Inspector'}, // {group: 'Structure', name: 'Create'}, // {group: 'Structure', name: 'Subplots'}, // {group: 'Structure', name: 'Transforms'}, // {group: 'Test', name: 'Testing'}, // {group: 'Style', name: 'General'}, // {group: 'Style', name: 'Traces'}, // {group: 'Style', name: 'Axes'}, // {group: 'Style', name: 'Legend'}, // {group: 'Style', name: 'Color Bars'}, // {group: 'Style', name: 'Annotation'}, // {group: 'Style', name: 'Shapes'}, // {group: 'Style', name: 'Images'}, // {group: 'Style', name: 'Sliders'}, // {group: 'Style', name: 'Menus'}, // ]} > <Panel group="Dev" name="JSON"> <div className="mocks"> <Select clearable={false} value={this.state.currentMockIndex} name="mock-dropdown" options={this.state.mocks.map((item, i) => ({ label: item, value: i, }))} searchable={true} searchPromptText="Search for a mock" onChange={(option) => this.loadMock(option.value)} noResultsText={'No Results'} placeholder={'Search for a mock'} /> </div> <button className="devbtn" onClick={this.loadJSON} style={{ background: this.state.json_error ? 'pink' : 'white' }} > Save </button> <AceEditor mode="json" theme="textmate" onChange={(json_string) => this.setState({ json_string })} value={this.state.json_string} name="UNIQUE_ID_OF_DIV" style={{ height: '80vh' }} setOptions={{ showLineNumbers: false, tabSize: 2, }} commands={[ { name: 'save', bindKey: { win: 'Ctrl-s', mac: 'Command-s' }, exec: this.loadJSON, }, ]} editorProps={{ $blockScrolling: true }} /> </Panel> <Panel group="Dev" name="Inspector"> <button className="devbtn" onClick={() => { const gd = document.getElementById('gd') || {}; this.setState({ full: { _fullData: gd._fullData || [], _fullLayout: gd._fullLayout || {}, }, }); }} > Refresh </button> <div style={{ height: '80vh' }}> <Inspector data={{ _full: this.state.full }} expandLevel={2} sortObjectKeys={true} /> </div> </Panel> </DefaultEditor> </PlotlyEditor> </div> ); } } export default hot(App);