diff --git a/dev/App.js b/dev/App.js index 85da19506..51260be8e 100644 --- a/dev/App.js +++ b/dev/App.js @@ -44,10 +44,18 @@ const config = {mapboxAccessToken: ACCESS_TOKENS.MAPBOX, editable: true}; const traceTypesConfig = { traces: _ => [ { - value: 'scattergl', + value: 'scatter', icon: 'scatter', label: _('Scatter'), }, + { + value: 'line', + label: _('Line'), + }, + { + value: 'area', + label: _('Area'), + }, { value: 'bar', label: _('Bar'), @@ -155,6 +163,7 @@ class App extends Component { debug advancedTraceTypeSelector showFieldTooltips + // glByDefault // traceTypesConfig={traceTypesConfig} // makeDefaultTrace={() => ({type: 'scattergl', mode: 'markers'})} > diff --git a/src/EditorControls.js b/src/EditorControls.js index 04b20aae0..3ddc20af5 100644 --- a/src/EditorControls.js +++ b/src/EditorControls.js @@ -49,6 +49,8 @@ class EditorControls extends Component { plotly: this.props.plotly, traceTypesConfig: this.props.traceTypesConfig, showFieldTooltips: this.props.showFieldTooltips, + glByDefault: this.props.glByDefault, + mapBoxAccess: this.props.mapBoxAccess, }; } @@ -121,7 +123,14 @@ class EditorControls extends Component { // can't use default prop because plotly.js mutates it: // https://github.com/plotly/react-chart-editor/issues/509 - graphDiv.data.push(this.props.makeDefaultTrace()); + graphDiv.data.push( + this.props.makeDefaultTrace + ? this.props.makeDefaultTrace() + : { + type: `scatter${this.props.glByDefault ? 'gl' : ''}`, + mode: 'markers', + } + ); if (this.props.afterAddTrace) { this.props.afterAddTrace(payload); @@ -308,12 +317,13 @@ EditorControls.propTypes = { showFieldTooltips: PropTypes.bool, traceTypesConfig: PropTypes.object, makeDefaultTrace: PropTypes.func, + glByDefault: PropTypes.bool, + mapBoxAccess: PropTypes.bool, }; EditorControls.defaultProps = { showFieldTooltips: false, locale: 'en', - makeDefaultTrace: () => ({type: 'scatter', mode: 'markers'}), traceTypesConfig: { categories: _ => categoryLayout(_), traces: _ => traceTypes(_), @@ -346,6 +356,8 @@ EditorControls.childContextTypes = { plotSchema: PropTypes.object, traceTypesConfig: PropTypes.object, showFieldTooltips: PropTypes.bool, + glByDefault: PropTypes.bool, + mapBoxAccess: PropTypes.bool, }; export default EditorControls; diff --git a/src/PlotlyEditor.js b/src/PlotlyEditor.js index 0bddbf082..054d3a9fe 100644 --- a/src/PlotlyEditor.js +++ b/src/PlotlyEditor.js @@ -27,6 +27,10 @@ class PlotlyEditor extends Component { showFieldTooltips={this.props.showFieldTooltips} srcConverters={this.props.srcConverters} makeDefaultTrace={this.props.makeDefaultTrace} + glByDefault={this.props.glByDefault} + mapBoxAccess={Boolean( + this.props.config && this.props.config.mapboxAccessToken + )} > {this.props.children} @@ -77,6 +81,7 @@ PlotlyEditor.propTypes = { fromSrc: PropTypes.func.isRequired, }), makeDefaultTrace: PropTypes.func, + glByDefault: PropTypes.bool, }; PlotlyEditor.defaultProps = { diff --git a/src/components/containers/Modal.js b/src/components/containers/Modal.js index 36f9dafdd..9b0e0b862 100644 --- a/src/components/containers/Modal.js +++ b/src/components/containers/Modal.js @@ -21,6 +21,26 @@ const ModalContent = ({children}) => ( ); class Modal extends Component { + constructor(props) { + super(props); + this.escFunction = this.escFunction.bind(this); + } + + escFunction(event) { + const escKeyCode = 27; + if (event.keyCode === escKeyCode) { + this.context.handleClose(); + } + } + + componentDidMount() { + document.addEventListener('keydown', this.escFunction, false); + } + + componentWillUnmount() { + document.removeEventListener('keydown', this.escFunction, false); + } + render() { const {children, title} = this.props; let classes = 'modal'; diff --git a/src/components/fields/TraceSelector.js b/src/components/fields/TraceSelector.js index 0021ed85f..6f97766cb 100644 --- a/src/components/fields/TraceSelector.js +++ b/src/components/fields/TraceSelector.js @@ -10,13 +10,24 @@ import { import TraceTypeSelector, { TraceTypeSelectorButton, } from 'components/widgets/TraceTypeSelector'; +import RadioBlocks from 'components/widgets/RadioBlocks'; + import Field from './Field'; +export const glAvailable = type => { + return ['scatter', 'scatterpolar', 'scattergl', 'scatterpolargl'].includes( + type + ); +}; + class TraceSelector extends Component { constructor(props, context) { super(props, context); this.updatePlot = this.updatePlot.bind(this); + this.setGl = this.setGl.bind(this); + this.glEnabled = this.glEnabled.bind(this); + this.setTraceDefaults = this.setTraceDefaults.bind(this); this.setTraceDefaults( props.container, @@ -26,6 +37,10 @@ class TraceSelector extends Component { this.setLocals(props, context); } + glEnabled() { + return this.props.container.type.endsWith('gl') ? 'gl' : ''; + } + setLocals(props, context) { const _ = context.localize; if (props.traceOptions) { @@ -46,10 +61,11 @@ class TraceSelector extends Component { } } - setTraceDefaults(container, fullContainer, updateContainer) { + setTraceDefaults(container, fullContainer, updateContainer, gl) { if (container && !container.mode && fullContainer.type === 'scatter') { updateContainer({ - type: 'scatter', + type: + 'scatter' + (gl || this.context.glByDefault ? gl : this.glEnabled()), mode: fullContainer.mode || 'markers', }); } @@ -63,11 +79,28 @@ class TraceSelector extends Component { updatePlot(value) { const {updateContainer} = this.props; + const {glByDefault} = this.context; if (updateContainer) { - updateContainer(traceTypeToPlotlyInitFigure(value)); + updateContainer( + traceTypeToPlotlyInitFigure(value, this.glEnabled() || glByDefault) + ); } } + setGl(value) { + const {container, fullContainer, updateContainer} = this.props; + const gl = 'gl'; + + this.setTraceDefaults(container, fullContainer, updateContainer, value); + + const traceType = + this.fullValue.endsWith(gl) && value === '' + ? this.fullValue.slice(0, -gl.length) + : this.fullValue; + + updateContainer(traceTypeToPlotlyInitFigure(traceType, value)); + } + render() { const props = Object.assign({}, this.props, { fullValue: this.fullValue, @@ -75,17 +108,41 @@ class TraceSelector extends Component { options: this.traceOptions, clearable: false, }); + const {localize: _, advancedTraceTypeSelector} = this.context; + + const options = [ + {label: _('SVG'), value: ''}, + {label: _('WebGl'), value: 'gl'}, + ]; + // Check and see if the advanced selector prop is true - const {advancedTraceTypeSelector} = this.context; if (advancedTraceTypeSelector) { return ( - - this.context.openModal(TraceTypeSelector, props)} - /> - +
+ + + this.context.openModal(TraceTypeSelector, { + ...props, + glByDefault: this.context.glByDefault, + }) + } + /> + + {!glAvailable(this.props.container.type) ? ( + '' + ) : ( + + + + )} +
); } @@ -100,6 +157,7 @@ TraceSelector.contextTypes = { plotSchema: PropTypes.object, config: PropTypes.object, localize: PropTypes.func, + glByDefault: PropTypes.bool, }; TraceSelector.propTypes = { diff --git a/src/components/widgets/TraceTypeSelector.js b/src/components/widgets/TraceTypeSelector.js index a6d5f3c48..cd2815e06 100644 --- a/src/components/widgets/TraceTypeSelector.js +++ b/src/components/widgets/TraceTypeSelector.js @@ -2,6 +2,7 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import {SearchIcon, ThumnailViewIcon, GraphIcon} from 'plotly-icons'; import Modal from 'components/containers/Modal'; +import {glAvailable} from 'components/fields/TraceSelector'; import { traceTypeToPlotlyInitFigure, renderTraceIcon, @@ -75,8 +76,20 @@ const Item = ({item, active, handleClick, actions, showActions, complex}) => { class TraceTypeSelector extends Component { selectAndClose(value) { + const { + updateContainer, + glByDefault, + fullContainer: {type}, + } = this.props; const computedValue = traceTypeToPlotlyInitFigure(value); - this.props.updateContainer(computedValue); + if ( + (type.endsWith('gl') || (!glAvailable(type) && glByDefault)) && + glAvailable(computedValue.type) && + !computedValue.type.endsWith('gl') + ) { + computedValue.type = computedValue.type + 'gl'; + } + updateContainer(computedValue); this.context.handleClose(); } @@ -84,13 +97,18 @@ class TraceTypeSelector extends Component { const {fullValue} = this.props; const { traceTypesConfig: {traces, categories, complex}, + mapBoxAccess, localize: _, } = this.context; return categories(_).map((category, i) => { - const items = traces(_).filter( - ({category: {value}}) => value === category.value - ); + let items = traces(_) + .filter(({category: {value}}) => value === category.value) + .filter(i => i.value !== 'scattergl' && i.value !== 'scatterpolargl'); + + if (!mapBoxAccess) { + items = items.filter(i => i.value !== 'scattermapbox'); + } const MAX_ITEMS = 4; @@ -207,11 +225,14 @@ export class TraceTypeSelectorButton extends Component { TraceTypeSelector.propTypes = { updateContainer: PropTypes.func, fullValue: PropTypes.string, + fullContainer: PropTypes.object, + glByDefault: PropTypes.bool, }; TraceTypeSelector.contextTypes = { traceTypesConfig: PropTypes.object, handleClose: PropTypes.func, localize: PropTypes.func, + mapBoxAccess: PropTypes.bool, }; TraceTypeSelectorButton.propTypes = { handleClick: PropTypes.func.isRequired, diff --git a/src/lib/customTraceType.js b/src/lib/customTraceType.js index d78efe9dc..f54feb6de 100644 --- a/src/lib/customTraceType.js +++ b/src/lib/customTraceType.js @@ -1,31 +1,37 @@ export function plotlyTraceToCustomTrace(trace) { - const type = trace.type || 'scatter'; + const gl = 'gl'; + const type = trace.type.endsWith(gl) + ? trace.type.slice(0, -gl.length) + : trace.type || 'scatter'; + if ( - type === 'scatter' && + (type === 'scatter' || type === 'scattergl') && ['tozeroy', 'tozerox', 'tonexty', 'tonextx', 'toself', 'tonext'].includes( trace.fill ) ) { return 'area'; } else if ( - type === 'scatter' && + (type === 'scatter' || type === 'scattergl') && (trace.mode === 'lines' || trace.mode === 'lines+markers') ) { return 'line'; } else if (type === 'scatter3d' && trace.mode === 'lines') { return 'line3d'; } - return trace.type; + return type; } -export function traceTypeToPlotlyInitFigure(traceType) { +export function traceTypeToPlotlyInitFigure(traceType, gl = '') { switch (traceType) { case 'line': - return {type: 'scatter', mode: 'lines', fill: 'none'}; + return {type: 'scatter' + gl, mode: 'lines', fill: 'none'}; case 'scatter': - return {type: 'scatter', mode: 'markers', fill: 'none'}; + return {type: 'scatter' + gl, mode: 'markers', fill: 'none'}; case 'area': - return {type: 'scatter', fill: 'tozeroy'}; + return {type: 'scatter' + gl, fill: 'tozeroy'}; + case 'scatterpolar': + return {type: 'scatterpolar' + gl}; case 'ohlc': return { type: 'ohlc', diff --git a/src/lib/index.js b/src/lib/index.js index 619ae35ef..cfc47cd32 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -58,7 +58,10 @@ function renderTraceIcon(trace, prefix = 'Plot') { if (!trace) { return null; } - const componentName = `${prefix}${pascalCase(trace)}Icon`; + const gl = 'gl'; + const componentName = `${prefix}${pascalCase( + trace.endsWith(gl) ? trace.slice(0, -gl.length) : trace + )}Icon`; return PlotlyIcons[componentName] ? PlotlyIcons[componentName] : PlotlyIcons.PlotLineIcon; diff --git a/src/lib/traceTypes.js b/src/lib/traceTypes.js index 4fb542e5c..98f78ef0c 100644 --- a/src/lib/traceTypes.js +++ b/src/lib/traceTypes.js @@ -12,10 +12,10 @@ export const chartCategory = _ => { value: 'CHARTS_3D', label: _('3D charts'), }, - // FINANCIAL: { - // value: 'FINANCIAL', - // label: _('Finance'), - // }, + FINANCIAL: { + value: 'FINANCIAL', + label: _('Finance'), + }, DISTRIBUTIONS: { value: 'DISTRIBUTIONS', label: _('Distributions'), @@ -28,9 +28,9 @@ export const chartCategory = _ => { value: 'SPECIALIZED', label: _('Specialized'), }, - WEB_GL: { - value: 'WEB_GL', - label: _('WebGL'), + THREE_D: { + value: '3D', + label: _('3D'), }, }; }; @@ -38,11 +38,11 @@ export const chartCategory = _ => { // Layout specification for TraceTypeSelector.js export const categoryLayout = _ => [ chartCategory(_).SIMPLE, - chartCategory(_).WEB_GL, chartCategory(_).DISTRIBUTIONS, - chartCategory(_).SPECIALIZED, + chartCategory(_).THREE_D, chartCategory(_).MAPS, - // chartCategory(_).FINANCIAL, + chartCategory(_).FINANCIAL, + chartCategory(_).SPECIALIZED, ]; export const traceTypes = _ => [ @@ -89,22 +89,22 @@ export const traceTypes = _ => [ { value: 'scatter3d', label: _('3D Scatter'), - category: chartCategory(_).WEB_GL, + category: chartCategory(_).THREE_D, }, { value: 'line3d', label: _('3D Line'), - category: chartCategory(_).WEB_GL, + category: chartCategory(_).THREE_D, }, { value: 'surface', label: _('3D Surface'), - category: chartCategory(_).WEB_GL, + category: chartCategory(_).THREE_D, }, { value: 'mesh3d', label: _('3D Mesh'), - category: chartCategory(_).WEB_GL, + category: chartCategory(_).THREE_D, }, { value: 'box', @@ -174,34 +174,34 @@ export const traceTypes = _ => [ { value: 'candlestick', label: _('Candlestick'), - category: chartCategory(_).SPECIALIZED, + category: chartCategory(_).FINANCIAL, }, { value: 'ohlc', label: _('OHLC'), - category: chartCategory(_).SPECIALIZED, + category: chartCategory(_).FINANCIAL, }, // { // value: 'pointcloud', // label: _('Point Cloud'), - // category: chartCategory(_).WEB_GL, + // category: chartCategory(_).THREE_D, // }, { value: 'scattergl', icon: 'scatter', - label: _('Scatter GL'), - category: chartCategory(_).WEB_GL, + label: _('Scatter'), + category: chartCategory(_).THREE_D, }, { value: 'scatterpolargl', icon: 'scatterpolar', - label: _('Polar Scatter GL'), - category: chartCategory(_).WEB_GL, + label: _('Polar Scatter'), + category: chartCategory(_).THREE_D, }, // { // value: 'heatmapgl', // icon: 'heatmap', // label: _('Heatmap GL'), - // category: chartCategory(_).WEB_GL, + // category: chartCategory(_).THREE_D, // }, ];