diff --git a/src/components/containers/TraceAccordion.js b/src/components/containers/TraceAccordion.js
index 5e4d1f521..8dd5a8a1a 100644
--- a/src/components/containers/TraceAccordion.js
+++ b/src/components/containers/TraceAccordion.js
@@ -11,7 +11,7 @@ const TraceFold = connectTraceToPlot(Fold);
class TraceAccordion extends Component {
render() {
- const {data = [], fullData = []} = this.context;
+ const {data = []} = this.context;
const {
canAdd,
canGroup,
@@ -52,31 +52,30 @@ class TraceAccordion extends Component {
);
}
- if (canGroup && data.length > 1) {
- const tracesByGroup = data.reduce((allTraces, next, index) => {
- const traceType = plotlyTraceToCustomTrace(
- fullData.filter(trace => trace.index === index)[0]
+ const tracesByGroup = data.reduce((allTraces, nextTrace, index) => {
+ const traceType = plotlyTraceToCustomTrace(nextTrace);
+ if (!allTraces[traceType]) {
+ allTraces[traceType] = [];
+ }
+ allTraces[traceType].push(index);
+ return allTraces;
+ }, {});
+
+ const groupedTraces = Object.keys(tracesByGroup)
+ .filter(traceType => !['ohlc', 'candlestick'].includes(traceType))
+ .map((traceType, index) => {
+ return (
+
+ {this.props.children}
+
);
- if (!allTraces[traceType]) {
- allTraces[traceType] = [];
- }
- allTraces[traceType].push(index);
- return allTraces;
- }, {});
+ });
- const groupedTraces = Object.keys(tracesByGroup).map(
- (traceType, index) => {
- return (
-
- {this.props.children}
-
- );
- }
- );
+ if (canGroup && data.length > 1 && groupedTraces.length > 0) {
return (
diff --git a/src/components/containers/derived.js b/src/components/containers/derived.js
index cd9e9c06a..6c94b1216 100644
--- a/src/components/containers/derived.js
+++ b/src/components/containers/derived.js
@@ -18,8 +18,9 @@ const TraceTypeSection = (props, context) => {
const {fullContainer} = context;
if (
fullContainer &&
- fullContainer._fullInput &&
- props.traceTypes.includes(fullContainer._fullInput.type)
+ ((fullContainer._fullInput &&
+ props.traceTypes.includes(fullContainer._fullInput.type)) ||
+ props.traceTypes.includes(fullContainer.type))
) {
return ;
}
diff --git a/src/components/fields/SymbolSelector.js b/src/components/fields/SymbolSelector.js
index efd438645..c216184b6 100644
--- a/src/components/fields/SymbolSelector.js
+++ b/src/components/fields/SymbolSelector.js
@@ -4,6 +4,7 @@ import React, {Component} from 'react';
import SymbolSelectorWidget from '../widgets/SymbolSelector';
import nestedProperty from 'plotly.js/src/lib/nested_property';
import {connectToContainer, tooLight} from 'lib';
+import {MULTI_VALUED} from '../../lib/constants';
// TODO compute these from plotly.js
const SYMBOLS = [
@@ -353,27 +354,38 @@ const SYMBOLS = [
];
class SymbolSelector extends Component {
- constructor(props) {
- super(props);
- this.setLocals(props);
+ constructor(props, context) {
+ super(props, context);
+ this.setLocals(props, context);
}
- componentWillReceiveProps(nextProps) {
- this.setLocals(nextProps);
+ componentWillReceiveProps(nextProps, nextContext) {
+ this.setLocals(nextProps, nextContext);
}
- setLocals(props) {
+ setLocals(props, context) {
const {fullContainer} = props;
+ const {defaultContainer} = context;
this.markerColor = nestedProperty(fullContainer, 'marker.color').get();
this.borderWidth = nestedProperty(fullContainer, 'marker.line.width').get();
+ if (this.markerColor === MULTI_VALUED) {
+ this.markerColor = nestedProperty(defaultContainer, 'marker.color').get();
+ }
+
this.borderColor = this.markerColor;
if (this.borderWidth) {
this.borderColor = nestedProperty(
fullContainer,
'marker.line.color'
).get();
+ if (this.borderColor === MULTI_VALUED) {
+ this.borderColor = nestedProperty(
+ defaultContainer,
+ 'marker.line.color'
+ ).get();
+ }
}
if (this.props.is3D) {
@@ -403,11 +415,14 @@ class SymbolSelector extends Component {
}
SymbolSelector.propTypes = {
- defaultValue: PropTypes.number,
+ defaultValue: PropTypes.string,
fullValue: PropTypes.any,
updatePlot: PropTypes.func,
...Field.propTypes,
};
+SymbolSelector.contextTypes = {
+ defaultContainer: PropTypes.object,
+};
SymbolSelector.defaultProps = {
showArrows: true,
diff --git a/src/components/fields/TextEditor.js b/src/components/fields/TextEditor.js
index 7bf39ee93..36a042e89 100644
--- a/src/components/fields/TextEditor.js
+++ b/src/components/fields/TextEditor.js
@@ -85,4 +85,10 @@ UnconnectedTextEditor.propTypes = {
export const LocalizedTextEditor = localize(UnconnectedTextEditor);
-export default connectToContainer(LocalizedTextEditor);
+export default connectToContainer(LocalizedTextEditor, {
+ modifyPlotProps: (props, context, plotProps) => {
+ if (plotProps.isVisible && plotProps.multiValued) {
+ plotProps.isVisible = false;
+ }
+ },
+});
diff --git a/src/default_panels/StyleTracesPanel.js b/src/default_panels/StyleTracesPanel.js
index 89425a00a..ecd2e0754 100644
--- a/src/default_panels/StyleTracesPanel.js
+++ b/src/default_panels/StyleTracesPanel.js
@@ -20,27 +20,27 @@ import {
TraceOrientation,
ColorscalePicker,
HoverInfo,
+ Dropdown,
FillDropdown,
+ FontSelector,
} from '../components';
import {localize} from '../lib';
const StyleTracesPanel = ({localize: _}) => (
-
+
+
(
+
+
+
+
+
+
+
1) {
+ const multiValuedContainer = deepCopyPublic(fullTrace);
+ fullData.forEach(t =>
+ Object.keys(t).forEach(key =>
+ setMultiValuedContainer(multiValuedContainer, t, key, {
+ searchArrays: true,
+ })
+ )
+ );
+ this.childContext.fullContainer = multiValuedContainer;
+ this.childContext.defaultContainer = fullTrace;
+ this.childContext.container = {};
+ }
+
if (trace && fullTrace) {
this.icon = renderTraceIcon(plotlyTraceToCustomTrace(trace));
this.name = fullTrace.name;
@@ -121,6 +136,7 @@ export default function connectTraceToPlot(WrappedComponent) {
getValObject: PropTypes.func,
updateContainer: PropTypes.func,
deleteContainer: PropTypes.func,
+ defaultContainer: PropTypes.object,
container: PropTypes.object,
fullContainer: PropTypes.object,
};
diff --git a/src/lib/multiValues.js b/src/lib/multiValues.js
new file mode 100644
index 000000000..388d05e37
--- /dev/null
+++ b/src/lib/multiValues.js
@@ -0,0 +1,77 @@
+import {MULTI_VALUED} from './constants';
+import {isPlainObject} from '../lib';
+
+/**
+ * Deep-copies the value using JSON. Underscored (private) keys are removed.
+ * @param {*} value Some nested value from the plotDiv object.
+ * @returns {*} A deepcopy of the value.
+ */
+function deepCopyPublic(value) {
+ if (typeof value === 'undefined') {
+ return value;
+ }
+
+ const skipPrivateKeys = (key, value) => (key.startsWith('_') ? 0 : value);
+
+ return window.JSON.parse(window.JSON.stringify(value, skipPrivateKeys));
+}
+
+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) === '_') ||
+ typeof intoVal === 'function' ||
+ key === 'module'
+ ) {
+ return;
+ }
+
+ // already a mixture of values, can't get any worse
+ if (intoVal === MULTI_VALUED) {
+ return;
+ } else if (intoVal === void 0) {
+ // if the original doesn't have the key it's because that key
+ // doesn't do anything there - so use the new value
+ // note that if fromObj doesn't have a key in intoObj we will not
+ // attempt to merge them at all, so this behavior makes the merge
+ // independent of order.
+ intoObj[key] = fromVal;
+ } else if (key === 'colorscale') {
+ // colorscales are arrays... need to stringify before comparing
+ // (other vals we don't want to stringify, as differences could
+ // potentially be real, like 'false' and false)
+ if (String(intoVal) !== String(fromVal)) {
+ intoObj[key] = MULTI_VALUED;
+ }
+ } else if (Array.isArray(intoVal)) {
+ // in data, other arrays are data, which we don't care about
+ // for styling purposes
+ if (!searchArrays) {
+ return;
+ }
+ // in layout though, we need to recurse into arrays
+ for (var i = 0; i < fromVal.length; i++) {
+ setMultiValuedContainer(intoVal, fromVal, i, searchArrays);
+ }
+ } else if (isPlainObject(fromVal)) {
+ // recurse into objects
+ if (!isPlainObject(intoVal)) {
+ throw new Error('tried to merge object into non-object: ' + key);
+ }
+ Object.keys(fromVal).forEach(function(key2) {
+ setMultiValuedContainer(intoVal, fromVal, key2, searchArrays);
+ });
+ } else if (isPlainObject(intoVal)) {
+ throw new Error('tried to merge non-object into object: ' + key);
+ } else if (intoVal !== fromVal) {
+ // different non-empty values -
+ intoObj[key] = MULTI_VALUED;
+ }
+}
+
+export {deepCopyPublic, setMultiValuedContainer};