From 707e90df61e15ea64344b19d2f64f10b722eaf3c Mon Sep 17 00:00:00 2001 From: VeraZab Date: Sun, 18 Feb 2018 21:12:23 -0500 Subject: [PATCH 01/23] Min adjustment --- src/components/containers/ImageAccordion.js | 4 ++-- src/components/containers/ShapeAccordion.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/containers/ImageAccordion.js b/src/components/containers/ImageAccordion.js index 850db31d8..c3c963318 100644 --- a/src/components/containers/ImageAccordion.js +++ b/src/components/containers/ImageAccordion.js @@ -13,8 +13,8 @@ class ImageAccordion extends Component { const content = images.length && - images.map((ann, i) => ( - + images.map((img, i) => ( + {children} )); diff --git a/src/components/containers/ShapeAccordion.js b/src/components/containers/ShapeAccordion.js index d3f872379..43f8db002 100644 --- a/src/components/containers/ShapeAccordion.js +++ b/src/components/containers/ShapeAccordion.js @@ -13,8 +13,8 @@ class ShapeAccordion extends Component { const content = shapes.length && - shapes.map((ann, i) => ( - + shapes.map((shp, i) => ( + {children} )); From 816d28259513323cc125ac949b8c32d578136f22 Mon Sep 17 00:00:00 2001 From: VeraZab Date: Sun, 18 Feb 2018 21:14:15 -0500 Subject: [PATCH 02/23] Export getAllAxes and adjust for subplots --- src/lib/__tests__/multiValued-test.js | 2 +- src/lib/connectAxesToLayout.js | 51 ++++++++------------------- src/lib/getAllAxes.js | 39 ++++++++++++++++++++ src/lib/index.js | 2 ++ 4 files changed, 57 insertions(+), 37 deletions(-) create mode 100644 src/lib/getAllAxes.js diff --git a/src/lib/__tests__/multiValued-test.js b/src/lib/__tests__/multiValued-test.js index 55ac08230..d0ed601eb 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.only('uses placeholder and empty string value', () => { + it('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 3b17d4b25..99c9463f0 100644 --- a/src/lib/connectAxesToLayout.js +++ b/src/lib/connectAxesToLayout.js @@ -2,14 +2,19 @@ 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, capitalize} from '../lib'; +import {capitalize, getAllAxes, getDisplayName, localize} from '../lib'; function computeAxesOptions(axes, _) { const options = [{label: _('All'), value: 'allaxes'}]; for (let i = 0; i < axes.length; i++) { const ax = axes[i]; const label = capitalize(ax._name.split('axis')[0]); - const value = (ax.prefix ? ax.prefix + '.' : '').trim() + ax._name; + const value = (ax.subplot && + !ax.subplot.includes('xaxis') && + !ax.subplot.includes('yaxis') + ? ax.subplot + '.' + ax._name + : ax.subplot + ).trim(); options[i + 1] = {label, value}; } @@ -37,37 +42,7 @@ export default function connectAxesToLayout(WrappedComponent) { const {container, fullContainer} = nextContext; const {axesTarget} = nextState; - 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.axes = getAllAxes(fullContainer); this.axesOptions = computeAxesOptions(this.axes, nextProps.localize); if (axesTarget === 'allaxes') { @@ -118,11 +93,15 @@ 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 prefix = axes[j].prefix; + const subplot = axes[j].subplot; let axesKey = axes[j]._name; - if (prefix) { - axesKey = `${prefix}.${axesKey}`; + if ( + subplot && + !subplot.includes('xaxis') && + !subplot.includes('yaxis') + ) { + axesKey = `${subplot}.${axesKey}`; } const newkey = `${axesKey}.${keys[i]}`; diff --git a/src/lib/getAllAxes.js b/src/lib/getAllAxes.js new file mode 100644 index 000000000..4025a2283 --- /dev/null +++ b/src/lib/getAllAxes.js @@ -0,0 +1,39 @@ +export default function getAllAxes(fullLayout) { + const axes = []; + // Plotly.js should really have a helper function for this, but until it does.. + if (fullLayout && fullLayout._subplots) { + Object.keys(fullLayout._subplots) + .filter( + // xaxis and yaxis already included separately in _fullLayout._subplots + type => type !== 'cartesian' && fullLayout._subplots[type].length !== 0 + ) + .forEach(type => { + fullLayout._subplots[type].forEach(subplot => { + if (['xaxis', 'yaxis'].includes(type)) { + // subplot will look like x2, x45, convert it to xaxis2, xaxis45 + subplot = // eslint-disable-line no-param-reassign + subplot.length > 1 + ? subplot.slice(0, 1) + 'axis' + subplot.slice(1) + : subplot + 'axis'; + + fullLayout[subplot].subplot = subplot; + axes.push(fullLayout[subplot]); + } else { + Object.keys(fullLayout[subplot]) + .filter(key => key.includes('axis')) + .forEach(axis => { + fullLayout[subplot][axis].subplot = subplot; + + // it should be in plotly.js, but it's not there for geo axes.. + if (!fullLayout[subplot][axis]._name) { + fullLayout[subplot][axis]._name = axis; + } + axes.push(fullLayout[subplot][axis]); + }); + } + }); + }); + } + + return axes; +} diff --git a/src/lib/index.js b/src/lib/index.js index 0a5816435..d5e451c92 100644 --- a/src/lib/index.js +++ b/src/lib/index.js @@ -10,6 +10,7 @@ import connectToContainer, { import connectTraceToPlot from './connectTraceToPlot'; import dereference from './dereference'; import findFullTraceIndex from './findFullTraceIndex'; +import getAllAxes from './getAllAxes'; import localize, {localizeString} from './localize'; import tinyColor from 'tinycolor2'; import unpackPlotProps from './unpackPlotProps'; @@ -62,6 +63,7 @@ export { traceTypeToPlotlyInitFigure, dereference, findFullTraceIndex, + getAllAxes, getDisplayName, getLayoutContext, isPlainObject, From 5dd25e8a8adcade953cba20cb156435c713318d4 Mon Sep 17 00:00:00 2001 From: VeraZab Date: Thu, 22 Feb 2018 23:25:40 -0500 Subject: [PATCH 03/23] Stop using child.type --- src/components/containers/Section.js | 5 +++-- src/components/fields/Info.js | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/containers/Section.js b/src/components/containers/Section.js index 82a8ef22b..77c83c8e5 100644 --- a/src/components/containers/Section.js +++ b/src/components/containers/Section.js @@ -1,4 +1,3 @@ -import Info from '../fields/Info'; import React, {Component, cloneElement} from 'react'; import PropTypes from 'prop-types'; import { @@ -51,7 +50,9 @@ class Section extends Component { // it will see plotProps and skip recomputing them. this.sectionVisible = this.sectionVisible || plotProps.isVisible; return cloneElement(child, {plotProps}); - } else if (child.type !== Info) { + } else if ( + !(child.type.plotly_editor_traits || {}).no_visibility_forcing + ) { // custom UI other than Info forces section visibility. this.sectionVisible = true; } diff --git a/src/components/fields/Info.js b/src/components/fields/Info.js index a3cd6734f..053b669b8 100644 --- a/src/components/fields/Info.js +++ b/src/components/fields/Info.js @@ -7,6 +7,10 @@ export default class Info extends Component { } } +Info.plotly_editor_traits = { + no_visibility_forcing: true, +}; + Info.propTypes = { ...Field.propTypes, }; From aee11ac7b6b474246bbcec25ac1510732d68f4a1 Mon Sep 17 00:00:00 2001 From: VeraZab Date: Fri, 23 Feb 2018 11:46:11 -0500 Subject: [PATCH 04/23] Create AxisCreator + Positionning controls --- src/components/containers/Section.js | 12 ++- src/components/fields/AxesSelector.js | 20 +++- src/components/fields/AxisCreator.js | 140 +++++++++++++++++++++++++ src/components/fields/Field.js | 13 +++ src/components/fields/derived.js | 21 ++++ src/components/fields/index.js | 4 + src/components/index.js | 4 + src/default_panels/GraphCreatePanel.js | 5 + src/default_panels/StyleAxesPanel.js | 40 +++---- src/lib/connectToContainer.js | 1 + src/lib/constants.js | 16 +++ src/lib/getAllAxes.js | 18 ++++ src/lib/index.js | 3 +- 13 files changed, 272 insertions(+), 25 deletions(-) create mode 100644 src/components/fields/AxisCreator.js diff --git a/src/components/containers/Section.js b/src/components/containers/Section.js index 77c83c8e5..2e4ef3f84 100644 --- a/src/components/containers/Section.js +++ b/src/components/containers/Section.js @@ -35,7 +35,17 @@ class Section extends Component { return null; } - if (child.props.attr) { + if ((child.type.plotly_editor_traits || {}).is_axis_creator) { + if (this.context.data.length > 1) { + this.sectionVisible = true; + return cloneElement(child, { + isVisible: true, + container: this.context.container, + fullContainer: this.context.fullContainer, + }); + } + this.sectionVisible = false; + } else if (child.props.attr) { let plotProps; if (child.type.supplyPlotProps) { plotProps = child.type.supplyPlotProps(child.props, nextContext); diff --git a/src/components/fields/AxesSelector.js b/src/components/fields/AxesSelector.js index 20af4504c..5aa230d77 100644 --- a/src/components/fields/AxesSelector.js +++ b/src/components/fields/AxesSelector.js @@ -1,5 +1,6 @@ import Field from './Field'; import PropTypes from 'prop-types'; +import Dropdown from '../widgets/Dropdown'; import RadioBlocks from '../widgets/RadioBlocks'; import React, {Component} from 'react'; @@ -19,11 +20,20 @@ export default class AxesSelector extends Component { return ( - + {axesOptions.length > 4 ? ( // eslint-disable-line no-magic-numbers + + ) : ( + + )} ); } diff --git a/src/components/fields/AxisCreator.js b/src/components/fields/AxisCreator.js new file mode 100644 index 000000000..6f07e0d46 --- /dev/null +++ b/src/components/fields/AxisCreator.js @@ -0,0 +1,140 @@ +import Dropdown from './Dropdown'; +import Field from './Field'; +import Info from './Info'; +import PropTypes from 'prop-types'; +import React, {Component, Fragment} from 'react'; +import {connectToContainer, localize, traceTypeToAxisType} from 'lib'; +import {PlusIcon} from 'plotly-icons'; + +export class UnconnectedAxisCreator extends Component { + constructor(props, context) { + super(props, context); + this.setLocals(props, context); + } + + componentWillReceiveProps(nextProps, nextContext) { + this.setLocals(nextProps, nextContext); + } + + setLocals(props, context) { + const _ = props.localize; + this.axisType = traceTypeToAxisType(props.container.type); + const isFirstTraceOfType = + context.data.filter(d => d.type === props.container.type).length === 1; + + function getNewSubplot(axis, subplot) { + const ok = isFirstTraceOfType + ? axis + : axis + (context.fullLayout._subplots[subplot].length + 1); + return ok; + } + + function getAxisControl(label, attr, subplot, update) { + const icon = ; + return ( + props.updateContainer(update), + }} + options={context.fullLayout._subplots[subplot].map(subplot => ({ + label: subplot, + value: subplot, + }))} + /> + ); + } + + if (this.axisType === 'cartesian') { + this.subplotControls = ( + + {getAxisControl(_('X axis'), 'xaxis', 'xaxis', { + xaxis: getNewSubplot('x', 'xaxis'), + })} + {getAxisControl(_('Y axis'), 'yaxis', 'yaxis', { + yaxis: getNewSubplot('y', 'yaxis'), + })} + + ); + } + + if (this.axisType === 'gl3d') { + this.subplotControls = getAxisControl( + _('Subplot to use'), + 'scene', + 'gl3d', + {geo: getNewSubplot('scene', 'gl3d')} + ); + } + + if (this.axisType === 'ternary') { + this.subplotControls = getAxisControl( + _('Subplot to use'), + 'subplot', + 'ternary', + {subplot: getNewSubplot('ternary', 'ternary')} + ); + } + + if (this.axisType === 'geo') { + this.subplotControls = getAxisControl(_('Subplot to use'), 'geo', 'geo', { + geo: getNewSubplot('geo', 'geo'), + }); + } + + if (this.axisType === 'mapbox') { + this.subplotControls = getAxisControl( + _('Subplot to use'), + 'subplot', + 'mapbox', + {subplot: getNewSubplot('mapbox', 'mapbox')} + ); + } + } + + renderSubplotControls() { + const {localize: _} = this.props; + return ( + + {this.subplotControls} + + {_('You can style and position your axes in the Style > Axes Panel')} + + + ); + } + + render() { + return {this.renderSubplotControls()}; + } +} + +UnconnectedAxisCreator.propTypes = { + attr: PropTypes.string, + localize: PropTypes.func, + container: PropTypes.object, + fullContainer: PropTypes.object, + updateContainer: PropTypes.func, +}; + +UnconnectedAxisCreator.contextTypes = { + fullLayout: PropTypes.object, + data: PropTypes.array, +}; + +UnconnectedAxisCreator.plotly_editor_traits = { + is_axis_creator: true, +}; + +function modifyPlotProps(props, context, plotProps) { + return plotProps.isVisible; +} +export default localize( + connectToContainer(UnconnectedAxisCreator, {modifyPlotProps}) +); diff --git a/src/components/fields/Field.js b/src/components/fields/Field.js index e3600c6de..987852295 100644 --- a/src/components/fields/Field.js +++ b/src/components/fields/Field.js @@ -5,6 +5,7 @@ import classnames from 'classnames'; import {bem, localize} from 'lib'; import {getMultiValueText} from 'lib/constants'; import {CloseIcon} from 'plotly-icons'; +import Button from '../widgets/Button'; export class FieldDelete extends Component { render() { @@ -20,6 +21,8 @@ export class FieldDelete extends Component { class Field extends Component { render() { const { + action, + onAction, center, children, label, @@ -58,6 +61,14 @@ class Field extends Component { ) : null} + {action ? ( +