diff --git a/README.md b/README.md index aed29b950..1d07f74af 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ Simple component that takes in props and renders. * ``: `` whose children are replicated into `` connected to traces via `connectTraceToPlot()`. * ``: `` whose children are connected to the `layout` figure key * ``: `` renders `` if no trace data is set +* ``: `` renders `` if no axis in `_fullLayout._subplots` * ``: `` whose children are replicated into `` connected to annotations via `connectAnnotationToLayout()`. For use in a ``. * ``: `` whose children are replicated into `` connected to shapes via `connectShapeToLayout()`. For use in a ``. * ``: `` whose children are replicated into `` connected to images via `connectImageToLayout()`. For use in a ``. diff --git a/src/components/containers/AxisRequiredPanel.js b/src/components/containers/AxisRequiredPanel.js new file mode 100644 index 000000000..e5a8a8ac3 --- /dev/null +++ b/src/components/containers/AxisRequiredPanel.js @@ -0,0 +1,51 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import PanelEmpty from './PanelEmpty'; +import Panel from './Panel'; + +class AxisRequiredPanel extends Component { + constructor(props) { + super(props); + this.state = { + hasAxis: true, + }; + } + + checkAxisExistence() { + const hasSubplot = + Object.keys(this.context.fullContainer._subplots).filter( + type => + !['cartesian', 'mapbox'].includes(type) && + this.context.fullContainer._subplots[type].length > 0 + ).length > 0; + if (!hasSubplot) { + this.setState({hasAxis: false}); + } + } + + componentWillReceiveProps() { + this.checkAxisExistence(); + } + + componentDidMount() { + this.checkAxisExistence(); + } + + render() { + if (this.state.hasAxis) { + return {this.props.children}; + } + return ; + } +} + +AxisRequiredPanel.propTypes = { + children: PropTypes.node, + emptyPanelHeader: PropTypes.string, +}; + +AxisRequiredPanel.contextTypes = { + fullContainer: PropTypes.object, +}; + +export default AxisRequiredPanel; diff --git a/src/components/containers/PanelEmpty.js b/src/components/containers/PanelEmpty.js index 403fadad6..30e78f5c8 100644 --- a/src/components/containers/PanelEmpty.js +++ b/src/components/containers/PanelEmpty.js @@ -1,17 +1,13 @@ import PropTypes from 'prop-types'; import React, {Component} from 'react'; import {ChartLineIcon} from 'plotly-icons'; -import {bem, localize} from 'lib'; +import {bem} from 'lib'; class PanelEmpty extends Component { render() { - const { - children, - localize: _, - heading = _("Looks like there aren't any traces defined yet."), - message = _("Go to the 'Create' tab to define traces."), - icon: Icon, - } = this.props; + const {children, icon: Icon} = this.props; + const heading = this.props.heading || ''; + const message = this.props.message || ''; return (
@@ -38,4 +34,4 @@ PanelEmpty.propTypes = { icon: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), }; -export default localize(PanelEmpty); +export default PanelEmpty; diff --git a/src/components/containers/TraceRequiredPanel.js b/src/components/containers/TraceRequiredPanel.js index 31bfc0893..140b8fd69 100644 --- a/src/components/containers/TraceRequiredPanel.js +++ b/src/components/containers/TraceRequiredPanel.js @@ -1,7 +1,8 @@ -import React, {Component} from 'react'; -import PropTypes from 'prop-types'; import PanelEmpty from './PanelEmpty'; +import PropTypes from 'prop-types'; +import React, {Component} from 'react'; import {LayoutPanel} from './derived'; +import {localize} from 'lib'; class TraceRequiredPanel extends Component { constructor(props) { @@ -36,14 +37,17 @@ class TraceRequiredPanel extends Component { } render() { - const {children, ...rest} = this.props; + const {localize: _, children, ...rest} = this.props; const {hasTraces} = this.state; if (this.props.visible) { return hasTraces ? ( {children} ) : ( - + ); } return null; @@ -52,6 +56,7 @@ class TraceRequiredPanel extends Component { TraceRequiredPanel.propTypes = { children: PropTypes.node, + localize: PropTypes.func, visible: PropTypes.bool, }; @@ -63,4 +68,4 @@ TraceRequiredPanel.contextTypes = { fullData: PropTypes.array, }; -export default TraceRequiredPanel; +export default localize(TraceRequiredPanel); diff --git a/src/components/containers/index.js b/src/components/containers/index.js index 5c8937f1e..8a7c373ed 100644 --- a/src/components/containers/index.js +++ b/src/components/containers/index.js @@ -2,6 +2,7 @@ import AnnotationAccordion from './AnnotationAccordion'; import ShapeAccordion from './ShapeAccordion'; import ImageAccordion from './ImageAccordion'; import AxesFold from './AxesFold'; +import AxisRequiredPanel from './AxisRequiredPanel'; import Fold from './Fold'; import MenuPanel from './MenuPanel'; import Panel from './Panel'; @@ -14,6 +15,7 @@ import SingleSidebarItem from './SingleSidebarItem'; export { AnnotationAccordion, + AxisRequiredPanel, ShapeAccordion, ImageAccordion, MenuPanel, diff --git a/src/components/index.js b/src/components/index.js index f118eac30..1d004da59 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -36,6 +36,7 @@ import { ShapeAccordion, ImageAccordion, AxesFold, + AxisRequiredPanel, Fold, LayoutPanel, MenuPanel, @@ -63,6 +64,7 @@ export { AxesFold, AxesRange, AxesSelector, + AxisRequiredPanel, Button, CanvasSize, ColorPicker, diff --git a/src/default_panels/StyleAxesPanel.js b/src/default_panels/StyleAxesPanel.js index f8fdaac71..785d28478 100644 --- a/src/default_panels/StyleAxesPanel.js +++ b/src/default_panels/StyleAxesPanel.js @@ -11,321 +11,335 @@ import { MenuPanel, Section, TraceRequiredPanel, + AxisRequiredPanel, AxesFold, Fold, + TraceTypeSection, } from '../components'; import {localize} from '../lib'; const StyleAxesPanel = ({localize: _}) => ( - - - - - - + + + + + + + - -
- - - - -
-
+ +
+ + + + +
+ + + + +
- -
- - - + +
+ + + - - -
-
- - - -
-
- - - -
+ + +
+
+ + + +
+
+ + + +
-
- - -
-
+
+ + +
+ + + +
+ + + + + + - -
+ +
+ +
+ +
+ +
+
+ +
+
+ + +
+ +
+ + + + + +
+
+ +
+ + + + +
+
+ + + + + +
+
+ + - - - - - - - -
- -
- -
- -
-
- -
-
- - + -
+ + -
+ + + +
+ + +
+
+ +
+
- - - -
-
- -
+ - - - -
-
- - - - -
-
- - - - - - - - - - - - - -
- - -
-
- -
-
- - - - - - - + + + +
); diff --git a/src/lib/__tests__/multiValued-test.js b/src/lib/__tests__/multiValued-test.js index d0ed601eb..55ac08230 100644 --- a/src/lib/__tests__/multiValued-test.js +++ b/src/lib/__tests__/multiValued-test.js @@ -7,7 +7,7 @@ import {connectLayoutToPlot, connectAxesToLayout} from '..'; import {mount} from 'enzyme'; describe('multiValued Numeric', () => { - it('uses placeholder and empty string value', () => { + it.only('uses placeholder and empty string value', () => { const fixtureProps = fixtures.scatter({ layout: {xaxis: {range: [0, 1]}, yaxis: {range: [-1, 1]}}, }); diff --git a/src/lib/connectAxesToLayout.js b/src/lib/connectAxesToLayout.js index a002535d8..3b17d4b25 100644 --- a/src/lib/connectAxesToLayout.js +++ b/src/lib/connectAxesToLayout.js @@ -2,16 +2,14 @@ import React, {Component} from 'react'; import PropTypes from 'prop-types'; import nestedProperty from 'plotly.js/src/lib/nested_property'; import {deepCopyPublic, setMultiValuedContainer} from './multiValues'; -import {getDisplayName, localize} from '../lib'; +import {getDisplayName, localize, capitalize} from '../lib'; function computeAxesOptions(axes, _) { const options = [{label: _('All'), value: 'allaxes'}]; for (let i = 0; i < axes.length; i++) { const ax = axes[i]; - const axesPrefix = ax._id.length > 1 ? ' ' + ax._id.substr(1) : ''; - const label = `${ax._id.charAt(0).toUpperCase()}${axesPrefix}`; - const value = - (axesPrefix.length > 0 ? axesPrefix + '.' : '').trim() + ax._name; + const label = capitalize(ax._name.split('axis')[0]); + const value = (ax.prefix ? ax.prefix + '.' : '').trim() + ax._name; options[i + 1] = {label, value}; } @@ -35,18 +33,41 @@ export default function connectAxesToLayout(WrappedComponent) { this.setLocals(nextProps, nextState, nextContext); } - // This function should be optimized. We can compare a list of - // axesNames to nextAxesNames and check gd.layout[axesN] for shallow - // equality. Unfortunately we are currently mutating gd.layout so the - // shallow check is not possible. setLocals(nextProps, nextState, nextContext) { - const {plotly, graphDiv, container, fullContainer} = nextContext; + const {container, fullContainer} = nextContext; const {axesTarget} = nextState; - if (plotly) { - this.axes = plotly.Axes.list(graphDiv); - } else { - this.axes = []; - } + + this.axes = []; + + // Plotly.js should really have a helper function for this, but until it does.. + Object.keys(fullContainer._subplots) + .filter( + // cartesian types will have xaxis or yaxis directly in _fullLayout + type => + type !== 'cartesian' && fullContainer._subplots[type].length !== 0 + ) + .forEach(type => { + if (['xaxis', 'yaxis'].includes(type)) { + this.axes.push(fullContainer[type]); + } + if (!['xaxis', 'yaxis', 'cartesian'].includes(type)) { + this.axes = Object.keys( + fullContainer[fullContainer._subplots[type]] + ) + .filter(key => key.includes('axis')) + .map(axis => { + // will take care of subplots after + const prefix = fullContainer._subplots[type][0]; + fullContainer[prefix][axis].prefix = prefix; + if (!fullContainer[prefix][axis]._name) { + // it should be in plotly.js, but it's not there for geo axes.. + fullContainer[prefix][axis]._name = axis; + } + return fullContainer[prefix][axis]; + }); + } + }); + this.axesOptions = computeAxesOptions(this.axes, nextProps.localize); if (axesTarget === 'allaxes') { @@ -60,11 +81,11 @@ export default function connectAxesToLayout(WrappedComponent) { ); this.fullContainer = multiValuedContainer; this.defaultContainer = this.axes[0]; - // what should this be set to? Probably doesn't matter. this.container = {}; } else { this.fullContainer = nestedProperty(fullContainer, axesTarget).get(); - this.container = nestedProperty(container, axesTarget).get(); + this.container = this.container = + nestedProperty(container, axesTarget).get() || {}; } } @@ -97,12 +118,11 @@ export default function connectAxesToLayout(WrappedComponent) { const keys = Object.keys(update); for (let i = 0; i < keys.length; i++) { for (let j = 0; j < axes.length; j++) { - const scene = axes[j]._id.substr(1); + const prefix = axes[j].prefix; let axesKey = axes[j]._name; - // scenes are nested - if (scene.indexOf('scene') !== -1) { - axesKey = `${scene}.${axesKey}`; + if (prefix) { + axesKey = `${prefix}.${axesKey}`; } const newkey = `${axesKey}.${keys[i]}`; @@ -134,8 +154,6 @@ export default function connectAxesToLayout(WrappedComponent) { AxesConnectedComponent.contextTypes = { container: PropTypes.object.isRequired, fullContainer: PropTypes.object.isRequired, - graphDiv: PropTypes.object.isRequired, - plotly: PropTypes.object, updateContainer: PropTypes.func, };