diff --git a/package.json b/package.json
index e5d31ad77..0c2771119 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"prop-types": "^15.5.10",
"raf": "^3.4.0",
"react-color": "^2.13.8",
- "react-colorscales": "0.5.7",
+ "react-colorscales": "0.5.8",
"react-dropzone": "^4.2.9",
"react-plotly.js": "^2.2.0",
"react-rangeslider": "^2.2.0",
diff --git a/src/components/containers/TraceAccordion.js b/src/components/containers/TraceAccordion.js
index 42f5389eb..9b7f863fe 100644
--- a/src/components/containers/TraceAccordion.js
+++ b/src/components/containers/TraceAccordion.js
@@ -86,17 +86,17 @@ class TraceAccordion extends Component {
- {_('All Traces')}
- {_('Individual')}
+ {_('Individually')}
+ {_('By Type')}
-
- {groupedTraces ? groupedTraces : null}
-
{individualTraces ? individualTraces : null}
+
+ {groupedTraces ? groupedTraces : null}
+
);
diff --git a/src/components/fields/Colorscale.js b/src/components/fields/Colorscale.js
index f8f817b2a..8b2058b08 100644
--- a/src/components/fields/Colorscale.js
+++ b/src/components/fields/Colorscale.js
@@ -35,6 +35,7 @@ class Colorscale extends Component {
);
@@ -44,7 +45,24 @@ class Colorscale extends Component {
Colorscale.propTypes = {
fullValue: PropTypes.any,
updatePlot: PropTypes.func,
+ initialCategory: PropTypes.string,
...Field.propTypes,
};
-export default connectToContainer(Colorscale);
+export default connectToContainer(Colorscale, {
+ modifyPlotProps: (props, context, plotProps) => {
+ if (
+ props.attr === 'marker.color' &&
+ context.fullData
+ .filter(t => context.traceIndexes.includes(t.index))
+ .every(t => t.marker && t.marker.color) &&
+ (plotProps.fullValue && typeof plotProps.fullValue === 'string')
+ ) {
+ plotProps.fullValue =
+ context.fullData &&
+ context.fullData
+ .filter(t => context.traceIndexes.includes(t.index))
+ .map(t => [0, t.marker.color]);
+ }
+ },
+});
diff --git a/src/components/fields/DataSelector.js b/src/components/fields/DataSelector.js
index d665a8c94..9ae425242 100644
--- a/src/components/fields/DataSelector.js
+++ b/src/components/fields/DataSelector.js
@@ -108,6 +108,7 @@ export class UnconnectedDataSelector extends Component {
optionRenderer={this.context.dataSourceOptionRenderer}
valueRenderer={this.context.dataSourceValueRenderer}
clearable={true}
+ placeholder={this.props.placeholder}
/>
);
@@ -118,6 +119,7 @@ UnconnectedDataSelector.propTypes = {
fullValue: PropTypes.any,
updatePlot: PropTypes.func,
container: PropTypes.object,
+ placeholder: PropTypes.string,
...Field.propTypes,
};
diff --git a/src/components/fields/MarkerColor.js b/src/components/fields/MarkerColor.js
index aa3a7c22c..f64369feb 100644
--- a/src/components/fields/MarkerColor.js
+++ b/src/components/fields/MarkerColor.js
@@ -7,9 +7,11 @@ import Color from './Color';
import Colorscale from './Colorscale';
import Numeric from './Numeric';
import Radio from './Radio';
+import Info from './Info';
import DataSelector from './DataSelector';
import VisibilitySelect from './VisibilitySelect';
import {MULTI_VALUED, COLORS} from 'lib/constants';
+import {getColorscale} from 'react-colorscales';
class UnconnectedMarkerColor extends Component {
constructor(props, context) {
@@ -36,28 +38,33 @@ class UnconnectedMarkerColor extends Component {
constant: type === 'constant' ? props.fullValue : COLORS.mutedBlue,
variable: type === 'variable' ? props.fullValue : null,
},
+ constantSelectedOption:
+ type === 'constant' && props.multiValued ? 'multiple' : 'single',
};
this.setType = this.setType.bind(this);
this.setValue = this.setValue.bind(this);
this.setColorScale = this.setColorScale.bind(this);
+ this.setColors = this.setColors.bind(this);
}
setType(type) {
- this.setState({type: type});
- this.props.updatePlot(this.state.value[type]);
- if (type === 'constant') {
- this.context.updateContainer({
- ['marker.colorsrc']: null,
- ['marker.colorscale']: null,
- });
- this.setState({colorscale: null});
- } else {
- this.context.updateContainer({
- ['marker.color']: null,
- ['marker.colorsrc']: null,
- ['marker.colorscale']: [],
- });
+ if (this.state.type !== type) {
+ this.setState({type: type});
+ this.props.updatePlot(this.state.value[type]);
+ if (type === 'constant') {
+ this.context.updateContainer({
+ ['marker.colorsrc']: null,
+ ['marker.colorscale']: null,
+ });
+ this.setState({colorscale: null});
+ } else {
+ this.context.updateContainer({
+ ['marker.color']: null,
+ ['marker.colorsrc']: null,
+ ['marker.colorscale']: [],
+ });
+ }
}
}
@@ -77,58 +84,158 @@ class UnconnectedMarkerColor extends Component {
this.context.updateContainer({['marker.colorscale']: inputValue});
}
+ isMultiValued() {
+ return (
+ this.props.multiValued ||
+ (Array.isArray(this.props.fullValue) &&
+ this.props.fullValue.includes(MULTI_VALUED)) ||
+ (this.props.container.marker &&
+ this.props.container.marker.colorscale === MULTI_VALUED) ||
+ (this.props.container.marker &&
+ this.props.container.marker.colorsrc === MULTI_VALUED) ||
+ (this.props.container.marker &&
+ this.props.container.marker.color &&
+ Array.isArray(this.props.container.marker.color) &&
+ this.props.container.marker.color.includes(MULTI_VALUED))
+ );
+ }
+
+ setColors(colorscale) {
+ const numberOfTraces = this.context.traceIndexes.length;
+ const colors = colorscale.map(c => c[1]);
+
+ let adjustedColors = getColorscale(colors, numberOfTraces);
+ if (adjustedColors.every(c => c === adjustedColors[0])) {
+ adjustedColors = colors;
+ }
+
+ const updates = adjustedColors.map(color => ({
+ ['marker.color']: color,
+ }));
+
+ this.setState({
+ colorscale: adjustedColors,
+ });
+
+ this.context.updateContainer(updates);
+ }
+
+ renderConstantControls() {
+ const _ = this.context.localize;
+ const constantOptions = [
+ {label: _('Single'), value: 'single'},
+ {label: _('Multiple'), value: 'multiple'},
+ ];
+
+ if (this.context.traceIndexes.length > 1) {
+ return (
+
+
+ this.setState({constantSelectedOption: value})
+ }
+ />
+
+ {this.state.constantSelectedOption === 'single'
+ ? _('All traces will be colored in the the same color.')
+ : _(
+ 'Each trace will be colored according to the selected colorscale.'
+ )}
+
+ {this.state.constantSelectedOption === 'single' ? (
+
+ ) : (
+
+ )}
+
+ );
+ }
+
+ return (
+
+ );
+ }
+
+ renderVariableControls() {
+ const _ = this.context.localize;
+ const multiValued =
+ (this.props.container &&
+ this.props.container.marker &&
+ (this.props.container.marker.colorscale &&
+ this.props.container.marker.colorscale === MULTI_VALUED)) ||
+ (this.props.container.marker.colorsrc &&
+ this.props.container.marker.colorsrc === MULTI_VALUED);
+ return (
+
+
+ {this.props.container.marker &&
+ this.props.container.marker.colorscale === MULTI_VALUED ? null : (
+
+ )}
+
+ );
+ }
+
render() {
- const {attr, fullValue, container} = this.props;
+ const {attr} = this.props;
const {localize: _} = this.context;
- const {type, value, colorscale} = this.state;
+ const {type} = this.state;
const options = [
{label: _('Constant'), value: 'constant'},
{label: _('Variable'), value: 'variable'},
];
- const multiValued =
- this.props.multiValued ||
- (Array.isArray(fullValue) && fullValue.includes(MULTI_VALUED)) ||
- (container.marker && container.marker.colorscale === MULTI_VALUED) ||
- (container.marker && container.marker.colorsrc === MULTI_VALUED) ||
- (container.marker &&
- container.marker.color &&
- Array.isArray(container.marker.color) &&
- container.marker.color.includes(MULTI_VALUED));
return (
-
-
- {!type ? null : type === 'constant' ? (
-
+
+
- ) : container.marker &&
- container.marker.colorsrc === MULTI_VALUED ? null : (
-
-
- {container.marker &&
- container.marker.colorscale === MULTI_VALUED ? null : (
-
- )}
-
- )}
+
+ {!type ? null : (
+
+ {type === 'constant'
+ ? _('All points in a trace are colored in the same color.')
+ : _('Each point in a trace is colored according to data.')}
+
+ )}
+
+
+ {!type
+ ? null
+ : type === 'constant'
+ ? this.renderConstantControls()
+ : this.renderVariableControls()}
- {type === 'constant' ? (
- ''
- ) : (
+ {type === 'constant' ? null : (
(
/>
-
+
1) {
@@ -108,13 +109,25 @@ export default function connectTraceToPlot(WrappedComponent) {
updateTrace(update) {
if (this.context.onUpdate) {
- this.context.onUpdate({
- type: EDITOR_ACTIONS.UPDATE_TRACES,
- payload: {
- update,
- traceIndexes: this.props.traceIndexes,
- },
- });
+ if (Array.isArray(update)) {
+ update.forEach((u, i) => {
+ this.context.onUpdate({
+ type: EDITOR_ACTIONS.UPDATE_TRACES,
+ payload: {
+ update: u,
+ traceIndexes: [this.props.traceIndexes[i]],
+ },
+ });
+ });
+ } else {
+ this.context.onUpdate({
+ type: EDITOR_ACTIONS.UPDATE_TRACES,
+ payload: {
+ update,
+ traceIndexes: this.props.traceIndexes,
+ },
+ });
+ }
}
}
@@ -156,6 +169,7 @@ export default function connectTraceToPlot(WrappedComponent) {
defaultContainer: PropTypes.object,
container: PropTypes.object,
fullContainer: PropTypes.object,
+ traceIndexes: PropTypes.array,
};
const {plotly_editor_traits} = WrappedComponent;
diff --git a/src/styles/components/fields/_main.scss b/src/styles/components/fields/_main.scss
index 3c7187a9b..2abaf1e4c 100644
--- a/src/styles/components/fields/_main.scss
+++ b/src/styles/components/fields/_main.scss
@@ -1,2 +1,3 @@
@import 'field';
@import 'symbolselector';
+@import 'markercolor';
diff --git a/src/styles/components/fields/_markercolor.scss b/src/styles/components/fields/_markercolor.scss
new file mode 100644
index 000000000..0ab02f03f
--- /dev/null
+++ b/src/styles/components/fields/_markercolor.scss
@@ -0,0 +1,3 @@
+.markercolor-constantcontrols__container {
+ margin-top: var(--spacing-quarter-unit);
+}
diff --git a/src/styles/components/widgets/_colorpicker.scss b/src/styles/components/widgets/_colorpicker.scss
index 362b7717e..522168bfa 100644
--- a/src/styles/components/widgets/_colorpicker.scss
+++ b/src/styles/components/widgets/_colorpicker.scss
@@ -22,7 +22,7 @@ $slider-picker-height: 10px;
padding: var(--spacing-eighth-unit);
&__popover {
- left: -90px;
+ left: -60px;
position: absolute;
top: 100%;
margin: var(--spacing-quarter-unit) 0 var(--spacing-base-unit);
@@ -71,13 +71,13 @@ $slider-picker-height: 10px;
}
&__custom-input {
padding: var(--spacing-quarter-unit) var(--spacing-half-unit);
- input{
+ input {
border: var(--border-default) !important;
box-shadow: none !important;
background-color: var(--color-background-inputs);
color: var(--color-text-dark);
}
- input + span{
+ input + span {
color: var(--color-text) !important;
}
}
@@ -117,9 +117,9 @@ $slider-picker-height: 10px;
&__preset-colors {
margin: 0 var(--spacing-half-unit);
- & > div{
+ & > div {
border-top: var(--border-light) !important; // inline style override
- padding:var(--spacing-half-unit) !important;
+ padding: var(--spacing-half-unit) !important;
padding-bottom: var(--spacing-quarter-unit) !important;
}
}
diff --git a/src/styles/components/widgets/_colorscalepicker.scss b/src/styles/components/widgets/_colorscalepicker.scss
index a6ac60709..f931114cc 100644
--- a/src/styles/components/widgets/_colorscalepicker.scss
+++ b/src/styles/components/widgets/_colorscalepicker.scss
@@ -24,5 +24,5 @@
}
.customPickerContainer {
- margin-top: 10px;
+ margin-top: var(--spacing-quarter-unit);
}