diff --git a/src/DefaultEditor.js b/src/DefaultEditor.js index 5139434f1..629b654e4 100644 --- a/src/DefaultEditor.js +++ b/src/DefaultEditor.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { AxesSelector, AxesRange, + CanvasSize, ColorPicker, DataSelector, Dropdown, @@ -17,6 +18,7 @@ import { MenuPanel, SymbolSelector, TraceAccordion, + TraceMarkerSection, TraceSelector, } from './components'; import {DEFAULT_FONTS} from './constants'; @@ -26,32 +28,6 @@ const LayoutPanel = connectLayoutToPlot(Panel); const AxesFold = connectAxesToLayout(Fold); class DefaultEditor extends Component { - constructor(props, context) { - super(props, context); - - const capitalize = s => s.charAt(0).toUpperCase() + s.substring(1); - - // Filter out Polar "area" type as it is fairly broken and we want to present - // scatter with fill as an "area" chart type for convenience. - const traceTypes = Object.keys(context.plotSchema.traces).filter( - t => t !== 'area' - ); - - const labels = traceTypes.map(capitalize); - this.traceOptions = traceTypes.map((t, i) => ({ - label: labels[i], - value: t, - })); - - const i = this.traceOptions.findIndex(opt => opt.value === 'scatter'); - this.traceOptions.splice( - i + 1, - 0, - {label: 'Line', value: 'line'}, - {label: 'Area', value: 'area'} - ); - } - render() { const _ = this.props.localize; @@ -63,7 +39,6 @@ class DefaultEditor extends Component { label="Plot Type" attr="type" clearable={false} - options={this.traceOptions} show /> @@ -115,6 +90,18 @@ class DefaultEditor extends Component { +
+ +
+
-
+ + -
+
@@ -168,12 +162,43 @@ class DefaultEditor extends Component { - + + + + + + + + + + @@ -212,21 +237,21 @@ class DefaultEditor extends Component {
- - - - - - - - - - - - - - - + {/* + + + + + + + + + + + + + + */} @@ -338,7 +363,6 @@ class DefaultEditor extends Component { DefaultEditor.contextTypes = { dataSourceNames: PropTypes.array.isRequired, - plotSchema: PropTypes.object.isRequired, }; export default localize(DefaultEditor); diff --git a/src/PlotlyEditor.js b/src/PlotlyEditor.js index 375cbb868..31383ac11 100644 --- a/src/PlotlyEditor.js +++ b/src/PlotlyEditor.js @@ -12,7 +12,9 @@ class PlotlyEditor extends Component { noShame({plotly: this.props.plotly}); // we only need to compute this once. - this.plotSchema = this.props.plotly.PlotSchema.get(); + if (this.props.plotly) { + this.plotSchema = this.props.plotly.PlotSchema.get(); + } } getChildContext() { @@ -51,7 +53,7 @@ class PlotlyEditor extends Component { PlotlyEditor.propTypes = { onUpdate: PropTypes.func, - plotly: PropTypes.object.isRequired, + plotly: PropTypes.object, graphDiv: PropTypes.object, locale: PropTypes.string, dataSources: PropTypes.object, @@ -72,8 +74,8 @@ PlotlyEditor.childContextTypes = { layout: PropTypes.object, locale: PropTypes.string, onUpdate: PropTypes.func, - plotSchema: PropTypes.object.isRequired, - plotly: PropTypes.object.isRequired, + plotSchema: PropTypes.object, + plotly: PropTypes.object, }; export default PlotlyEditor; diff --git a/src/components/containers/Section.js b/src/components/containers/Section.js index b5e8aaeee..264f390ad 100644 --- a/src/components/containers/Section.js +++ b/src/components/containers/Section.js @@ -45,9 +45,14 @@ class Section extends Component { } const isAttr = Boolean(child.props.attr); - const plotProps = isAttr - ? unpackPlotProps(child.props, context, child.type) - : {isVisible: true}; + let plotProps; + if (child.plotProps) { + plotProps = child.plotProps; + } else if (isAttr) { + plotProps = unpackPlotProps(child.props, context, child.type); + } else { + plotProps = {isVisible: true}; + } const childProps = Object.assign({plotProps}, child.props); childProps.key = i; attrChildren.push(cloneElement(child, childProps)); diff --git a/src/components/containers/TraceAccordion.js b/src/components/containers/TraceAccordion.js index ae9cddfae..1b024861c 100644 --- a/src/components/containers/TraceAccordion.js +++ b/src/components/containers/TraceAccordion.js @@ -7,38 +7,34 @@ import {connectTraceToPlot} from '../../lib'; const TraceFold = connectTraceToPlot(Fold); export default class TraceAccordion extends Component { - constructor(props, context) { + constructor(props) { super(props); this.addTrace = this.addTrace.bind(this); - this.renderPanel = this.renderPanel.bind(this); - } - - renderPanel(d, i) { - return ( - - {this.props.children} - - ); } addTrace() { - this.context.onUpdate && + if (this.context.onUpdate) { this.context.onUpdate({ type: EDITOR_ACTIONS.ADD_TRACE, }); + } } render() { const data = this.context.data || []; return (
- {this.props.canAdd && ( + {this.props.canAdd ? ( Add - )} - {data.map(this.renderPanel)} + ) : null} + {data.map((d, i) => ( + + {this.props.children} + + ))}
); } @@ -50,5 +46,6 @@ TraceAccordion.contextTypes = { }; TraceAccordion.propTypes = { + children: PropTypes.node, canAdd: PropTypes.bool, }; diff --git a/src/components/containers/TraceMarkerSection.js b/src/components/containers/TraceMarkerSection.js new file mode 100644 index 000000000..287ea7665 --- /dev/null +++ b/src/components/containers/TraceMarkerSection.js @@ -0,0 +1,41 @@ +import Section from './Section'; +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import localize from '../../lib/localize'; + +class TraceMarkerSection extends Component { + constructor(props, context) { + super(props, context); + this.setLocals(context); + } + + componentWillReceiveProps(nextProps, nextContext) { + this.setLocals(nextContext); + } + + setLocals(context) { + const _ = this.props.localize; + const traceType = context.fullContainer.type; + if (traceType === 'bar') { + this.name = _('Bars'); + } else { + this.name = _('Points'); + } + } + + render() { + return
{this.props.children}
; + } +} + +TraceMarkerSection.propTypes = { + children: PropTypes.node, + localize: PropTypes.func, + name: PropTypes.string, +}; + +TraceMarkerSection.contextTypes = { + fullContainer: PropTypes.object, +}; + +export default localize(TraceMarkerSection); diff --git a/src/components/containers/__tests__/Layout-test.js b/src/components/containers/__tests__/Layout-test.js index 3597717fc..a24cadf30 100644 --- a/src/components/containers/__tests__/Layout-test.js +++ b/src/components/containers/__tests__/Layout-test.js @@ -3,13 +3,13 @@ import {Fold, Panel, Section} from '..'; import NumericInput from '../../widgets/NumericInputStatefulWrapper'; import React from 'react'; import {EDITOR_ACTIONS} from '../../../constants'; -import {TestEditor, fixtures, plotly} from '../../../lib/test-utils'; +import {TestEditor, fixtures} from '../../../lib/test-utils'; import {connectLayoutToPlot} from '../../../lib'; import {mount} from 'enzyme'; const Layouts = [Panel, Fold, Section].map(connectLayoutToPlot); const Editor = props => ( - + ); Layouts.forEach(Layout => { diff --git a/src/components/containers/__tests__/Section-test.js b/src/components/containers/__tests__/Section-test.js index 6ab8826e3..3bb47a7e0 100644 --- a/src/components/containers/__tests__/Section-test.js +++ b/src/components/containers/__tests__/Section-test.js @@ -2,7 +2,7 @@ import React from 'react'; import Section from '../Section'; import MenuPanel from '../MenuPanel'; import {Flaglist, Info, Numeric} from '../../fields'; -import {TestEditor, fixtures, plotly} from '../../../lib/test-utils'; +import {TestEditor, fixtures} from '../../../lib/test-utils'; import {connectTraceToPlot} from '../../../lib'; import {mount} from 'enzyme'; @@ -12,11 +12,7 @@ describe('Section', () => { it('is visible if it contains any visible children', () => { // mode is visible with scatter. Hole is not visible. Section should show. const wrapper = mount( - + { it('is visible if it contains any non attr children', () => { const wrapper = mount( - +
INFO
@@ -69,11 +61,7 @@ describe('Section', () => { it('is not visible if it contains no visible children', () => { // pull and hole are not scatter attrs. Section should not show. const wrapper = mount( - + @@ -93,11 +81,7 @@ describe('Section', () => { it('will render first menuPanel even with no visible attrs', () => { const wrapper = mount( - +
INFO diff --git a/src/components/containers/index.js b/src/components/containers/index.js index 73952bf73..7284a63eb 100644 --- a/src/components/containers/index.js +++ b/src/components/containers/index.js @@ -3,5 +3,6 @@ import Fold from './Fold'; import Panel from './Panel'; import Section from './Section'; import TraceAccordion from './TraceAccordion'; +import TraceMarkerSection from './TraceMarkerSection'; -export {MenuPanel, Fold, Panel, Section, TraceAccordion}; +export {MenuPanel, Fold, Panel, Section, TraceAccordion, TraceMarkerSection}; diff --git a/src/components/fields/CanvasSize.js b/src/components/fields/CanvasSize.js new file mode 100644 index 000000000..23de502b1 --- /dev/null +++ b/src/components/fields/CanvasSize.js @@ -0,0 +1,25 @@ +import Numeric from './Numeric'; +import React, {Component} from 'react'; +import {connectToContainer} from '../../lib'; + +class CanvasSize extends Component { + static modifyPlotProps(props, context, plotProps) { + if (!plotProps.isVisible) { + return; + } + const {fullContainer} = plotProps; + if (fullContainer && fullContainer.autosize) { + plotProps.isVisible = false; + } + } + + render() { + return ; + } +} + +CanvasSize.propTypes = { + ...Numeric.propTypes, +}; + +export default connectToContainer(CanvasSize); diff --git a/src/components/fields/TraceSelector.js b/src/components/fields/TraceSelector.js index a5dbb585b..95dec45b7 100644 --- a/src/components/fields/TraceSelector.js +++ b/src/components/fields/TraceSelector.js @@ -4,20 +4,70 @@ import React, {Component} from 'react'; import nestedProperty from 'plotly.js/src/lib/nested_property'; import {connectToContainer} from '../../lib'; +function computeTraceOptionsFromSchema(schema) { + const capitalize = s => s.charAt(0).toUpperCase() + s.substring(1); + + // Filter out Polar "area" type as it is fairly broken and we want to present + // scatter with fill as an "area" chart type for convenience. + const traceTypes = Object.keys(schema.traces).filter(t => t !== 'area'); + + const labels = traceTypes.map(capitalize); + const traceOptions = traceTypes.map((t, i) => ({ + label: labels[i], + value: t, + })); + + const i = traceOptions.findIndex(opt => opt.value === 'scatter'); + traceOptions.splice( + i + 1, + 0, + {label: 'Line', value: 'line'}, + {label: 'Area', value: 'area'} + ); + + return traceOptions; +} + class TraceSelector extends Component { - constructor(props) { - super(props); + constructor(props, context) { + super(props, context); this.updatePlot = this.updatePlot.bind(this); this.fullValue = this.fullValue.bind(this); - const fillMeta = props.getValObject('fill'); + let fillMeta; + if (props.getValObject) { + fillMeta = props.getValObject('fill'); + } if (fillMeta) { this.fillTypes = fillMeta.values.filter(v => v !== 'none'); } else { - this.fillTypes = []; + this.fillTypes = [ + 'tozeroy', + 'tozerox', + 'tonexty', + 'tonextx', + 'toself', + 'tonext', + ]; + } + + this.setLocals(props, context); + } + + setLocals(props, context) { + if (props.traceOptions) { + this.traceOptions = props.traceOptions; + } else if (context.plotSchema) { + this.traceOptions = computeTraceOptionsFromSchema(context.plotSchema); + } else { + this.traceOptions = [{label: 'Scatter', value: 'scatter'}]; } } + componentWillReceiveProps(nextProps, nextContext) { + this.setLocals(nextProps, nextContext); + } + updatePlot(value) { let update; if (value === 'line') { @@ -59,14 +109,19 @@ class TraceSelector extends Component { const props = Object.assign({}, this.props, { fullValue: this.fullValue, updatePlot: this.updatePlot, + options: this.traceOptions, }); return ; } } +TraceSelector.contextTypes = { + plotSchema: PropTypes.object, +}; + TraceSelector.propTypes = { - getValObject: PropTypes.func.isRequired, + getValObject: PropTypes.func, container: PropTypes.object.isRequired, fullValue: PropTypes.func.isRequired, updateContainer: PropTypes.func, diff --git a/src/components/fields/index.js b/src/components/fields/index.js index 631d12783..2d3d9e199 100644 --- a/src/components/fields/index.js +++ b/src/components/fields/index.js @@ -1,5 +1,6 @@ import AxesRange from './AxesRange'; import AxesSelector from './AxesSelector'; +import CanvasSize from './CanvasSize'; import ColorPicker from './Color'; import Dropdown from './Dropdown'; import Flaglist from './Flaglist'; @@ -13,6 +14,7 @@ import TraceSelector from './TraceSelector'; export { AxesRange, AxesSelector, + CanvasSize, ColorPicker, Dropdown, Flaglist, diff --git a/src/components/index.js b/src/components/index.js index c594c8c0b..42ac7373e 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,6 +1,7 @@ import { AxesRange, AxesSelector, + CanvasSize, ColorPicker, Dropdown, Flaglist, @@ -12,13 +13,21 @@ import { TraceSelector, } from './fields'; -import {MenuPanel, Fold, Panel, Section, TraceAccordion} from './containers'; +import { + MenuPanel, + Fold, + Panel, + Section, + TraceAccordion, + TraceMarkerSection, +} from './containers'; import PanelMenuWrapper from './PanelMenuWrapper'; export { AxesSelector, AxesRange, MenuPanel, + CanvasSize, ColorPicker, DataSelector, Dropdown, @@ -32,5 +41,6 @@ export { Section, SymbolSelector, TraceAccordion, + TraceMarkerSection, TraceSelector, }; diff --git a/src/index.js b/src/index.js index cf273d77c..15c16f022 100644 --- a/src/index.js +++ b/src/index.js @@ -12,6 +12,7 @@ import {EDITOR_ACTIONS} from './constants'; import { AxesRange, AxesSelector, + CanvasSize, ColorPicker, DataSelector, Dropdown, @@ -27,6 +28,7 @@ import { MenuPanel, SymbolSelector, TraceAccordion, + TraceMarkerSection, TraceSelector, } from './components'; @@ -34,6 +36,7 @@ export { AxesRange, AxesSelector, MenuPanel, + CanvasSize, ColorPicker, DataSelector, Dropdown, @@ -50,6 +53,7 @@ export { Section, SymbolSelector, TraceAccordion, + TraceMarkerSection, TraceSelector, connectAxesToLayout, connectLayoutToPlot, diff --git a/src/lib/connectAxesToLayout.js b/src/lib/connectAxesToLayout.js index 27e38ba9c..2af048572 100644 --- a/src/lib/connectAxesToLayout.js +++ b/src/lib/connectAxesToLayout.js @@ -34,10 +34,12 @@ function deepCopyPublic(value) { /* * Test that we can connectLayoutToPlot(connectAxesToLayout(Panel)) */ -function setMultiValuedContainer(intoObj, fromObj, key, searchArrays) { +function setMultiValuedContainer(intoObj, fromObj, key, config = {}) { var intoVal = intoObj[key], fromVal = fromObj[key]; + var searchArrays = config.searchArrays; + // don't merge private attrs if ( (typeof key === 'string' && key.charAt(0) === '_') || @@ -126,18 +128,22 @@ export default function connectAxesToLayout(WrappedComponent) { setLocals(nextProps, nextState, nextContext) { const {plotly, graphDiv, container, fullContainer} = nextContext; const {axesTarget} = nextState; - this.axes = plotly.Axes.list(graphDiv); + if (plotly) { + this.axes = plotly.Axes.list(graphDiv); + } else { + this.axes = []; + } this.axesOptions = computeAxesOptions(fullContainer, this.axes); if (axesTarget === 'allaxes') { const multiValuedContainer = deepCopyPublic(this.axes[0]); - this.axes - .slice(1) - .forEach(ax => - Object.keys(ax).forEach(key => - setMultiValuedContainer(multiValuedContainer, ax, key) - ) - ); + this.axes.slice(1).forEach(ax => + Object.keys(ax).forEach(key => + setMultiValuedContainer(multiValuedContainer, ax, key, { + searchArrays: true, + }) + ) + ); this.fullContainer = multiValuedContainer; this.defaultContainer = this.axes[0]; // what should this be set to? Probably doesn't matter. @@ -214,8 +220,8 @@ export default function connectAxesToLayout(WrappedComponent) { container: PropTypes.object.isRequired, fullContainer: PropTypes.object.isRequired, graphDiv: PropTypes.object.isRequired, - plotly: PropTypes.object.isRequired, - updateContainer: PropTypes.func.isRequired, + plotly: PropTypes.object, + updateContainer: PropTypes.func, }; AxesConnectedComponent.childContextTypes = { diff --git a/src/lib/connectLayoutToPlot.js b/src/lib/connectLayoutToPlot.js index 3bbe3ea41..00e474c52 100644 --- a/src/lib/connectLayoutToPlot.js +++ b/src/lib/connectLayoutToPlot.js @@ -14,12 +14,17 @@ export default function connectLayoutToPlot(WrappedComponent) { getChildContext() { const {layout, fullLayout, plotly} = this.context; - return { - getValObject: attr => + + let getValObject; + if (plotly) { + getValObject = attr => plotly.PlotSchema.getLayoutValObject( fullLayout, nestedProperty({}, attr).parts - ), + ); + } + return { + getValObject, updateContainer: this.updateContainer, container: layout, fullContainer: fullLayout, @@ -48,7 +53,7 @@ export default function connectLayoutToPlot(WrappedComponent) { LayoutConnectedComponent.contextTypes = { layout: PropTypes.object, fullLayout: PropTypes.object, - plotly: PropTypes.object.isRequired, + plotly: PropTypes.object, onUpdate: PropTypes.func, }; diff --git a/src/lib/connectTraceToPlot.js b/src/lib/connectTraceToPlot.js index d581ded89..4d2c8120b 100644 --- a/src/lib/connectTraceToPlot.js +++ b/src/lib/connectTraceToPlot.js @@ -20,12 +20,18 @@ export default function connectTraceToPlot(WrappedComponent) { const trace = data[traceIndex] || {}; const fullTraceIndex = findFullTraceIndex(fullData, traceIndex); const fullTrace = fullData[fullTraceIndex] || {}; - return { - getValObject: attr => + + let getValObject; + if (plotly) { + getValObject = attr => plotly.PlotSchema.getTraceValObject( fullTrace, nestedProperty({}, attr).parts - ), + ); + } + + return { + getValObject, updateContainer: this.updateTrace, deleteContainer: this.deleteTrace, container: trace, @@ -70,7 +76,7 @@ export default function connectTraceToPlot(WrappedComponent) { TraceConnectedComponent.contextTypes = { fullData: PropTypes.array, data: PropTypes.array, - plotly: PropTypes.object.isRequired, + plotly: PropTypes.object, onUpdate: PropTypes.func, }; diff --git a/src/lib/unpackPlotProps.js b/src/lib/unpackPlotProps.js index 46f6190cd..1d53e5b0f 100644 --- a/src/lib/unpackPlotProps.js +++ b/src/lib/unpackPlotProps.js @@ -34,7 +34,10 @@ export default function unpackPlotProps(props, context, ComponentClass) { } // Property descriptions and meta: - const attrMeta = context.getValObject(props.attr) || {}; + let attrMeta; + if (getValObject) { + attrMeta = context.getValObject(props.attr) || {}; + } // Update data functions: const updatePlot = v => updateContainer && updateContainer({[props.attr]: v}); @@ -61,11 +64,13 @@ export default function unpackPlotProps(props, context, ComponentClass) { multiValued, }; - if (isNumeric(attrMeta.max)) { - plotProps.max = attrMeta.max; - } - if (isNumeric(attrMeta.min)) { - plotProps.min = attrMeta.min; + if (attrMeta) { + if (isNumeric(attrMeta.max)) { + plotProps.max = attrMeta.max; + } + if (isNumeric(attrMeta.min)) { + plotProps.min = attrMeta.min; + } } // Give Component Classes the space to modify plotProps: diff --git a/src/styles/components/fields/_field.scss b/src/styles/components/fields/_field.scss index 520945caa..ef5a68dd0 100644 --- a/src/styles/components/fields/_field.scss +++ b/src/styles/components/fields/_field.scss @@ -35,7 +35,7 @@ .field__widget { width: 64%; - padding-left: 18px; + padding-left: 12px; display: inline-block; padding-right: 12px; } diff --git a/src/styles/components/widgets/_numeric-input.scss b/src/styles/components/widgets/_numeric-input.scss index aca76a1a7..853bf29c7 100644 --- a/src/styles/components/widgets/_numeric-input.scss +++ b/src/styles/components/widgets/_numeric-input.scss @@ -14,7 +14,7 @@ text-align: left; border-radius: 2px; padding: 6px 6px 6px 12px; - width: 80px; + width: 62px; vertical-align: middle; font-size: inherit; color: inherit;