Skip to content

Commit 75d142d

Browse files
committed
Enable batch trace colouring
1 parent b771962 commit 75d142d

File tree

12 files changed

+176
-56
lines changed

12 files changed

+176
-56
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
"prop-types": "^15.5.10",
2020
"raf": "^3.4.0",
2121
"react-color": "^2.13.8",
22-
"react-colorscales": "0.5.7",
22+
"react-colorscales": "0.5.8",
2323
"react-dropzone": "^4.2.9",
2424
"react-plotly.js": "^2.2.0",
2525
"react-rangeslider": "^2.2.0",

src/components/fields/Colorscale.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,14 @@ Colorscale.propTypes = {
4747
...Field.propTypes,
4848
};
4949

50-
export default connectToContainer(Colorscale);
50+
export default connectToContainer(Colorscale, {
51+
modifyPlotProps: (props, context, plotProps) => {
52+
if (plotProps.fullValue && typeof plotProps.fullValue === 'string') {
53+
plotProps.fullValue =
54+
context.fullData &&
55+
context.fullData
56+
.filter(t => context.traceIndexes.includes(t.index))
57+
.map(t => [0, t.marker.color]);
58+
}
59+
},
60+
});

src/components/fields/DataSelector.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ export class UnconnectedDataSelector extends Component {
108108
optionRenderer={this.context.dataSourceOptionRenderer}
109109
valueRenderer={this.context.dataSourceValueRenderer}
110110
clearable={true}
111+
placeholder={this.props.placeholder}
111112
/>
112113
</Field>
113114
);
@@ -118,6 +119,7 @@ UnconnectedDataSelector.propTypes = {
118119
fullValue: PropTypes.any,
119120
updatePlot: PropTypes.func,
120121
container: PropTypes.object,
122+
placeholder: PropTypes.string,
121123
...Field.propTypes,
122124
};
123125

src/components/fields/MarkerColor.js

Lines changed: 125 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import Color from './Color';
77
import Colorscale from './Colorscale';
88
import Numeric from './Numeric';
99
import Radio from './Radio';
10+
import Info from './Info';
1011
import DataSelector from './DataSelector';
1112
import VisibilitySelect from './VisibilitySelect';
1213
import {MULTI_VALUED, COLORS} from 'lib/constants';
14+
import {getColorscale} from 'react-colorscales';
1315

1416
class UnconnectedMarkerColor extends Component {
1517
constructor(props, context) {
@@ -36,11 +38,14 @@ class UnconnectedMarkerColor extends Component {
3638
constant: type === 'constant' ? props.fullValue : COLORS.mutedBlue,
3739
variable: type === 'variable' ? props.fullValue : null,
3840
},
41+
constantSelectedOption:
42+
type === 'constant' && props.multiValued ? 'multiple' : 'single',
3943
};
4044

4145
this.setType = this.setType.bind(this);
4246
this.setValue = this.setValue.bind(this);
4347
this.setColorScale = this.setColorScale.bind(this);
48+
this.setColors = this.setColors.bind(this);
4449
}
4550

4651
setType(type) {
@@ -77,58 +82,141 @@ class UnconnectedMarkerColor extends Component {
7782
this.context.updateContainer({['marker.colorscale']: inputValue});
7883
}
7984

85+
isMultiValued() {
86+
return (
87+
this.props.multiValued ||
88+
(Array.isArray(this.props.fullValue) &&
89+
this.props.fullValue.includes(MULTI_VALUED)) ||
90+
(this.props.container.marker &&
91+
this.props.container.marker.colorscale === MULTI_VALUED) ||
92+
(this.props.container.marker &&
93+
this.props.container.marker.colorsrc === MULTI_VALUED) ||
94+
(this.props.container.marker &&
95+
this.props.container.marker.color &&
96+
Array.isArray(this.props.container.marker.color) &&
97+
this.props.container.marker.color.includes(MULTI_VALUED))
98+
);
99+
}
100+
101+
setColors(colorscale) {
102+
const numberOfTraces = this.context.traceIndexes.length;
103+
const adjustedScale = getColorscale(
104+
colorscale.map(c => c[1]),
105+
numberOfTraces
106+
);
107+
const updates = adjustedScale.map(color => ({
108+
['marker.color']: color,
109+
}));
110+
this.setState({
111+
colorscale: adjustedScale,
112+
});
113+
this.context.updateContainer(updates);
114+
}
115+
116+
renderConstantControls() {
117+
const _ = this.context.localize;
118+
const constantOptions = [
119+
{label: _('Single'), value: 'single'},
120+
{label: _('Multiple'), value: 'multiple'},
121+
];
122+
123+
if (this.context.traceIndexes.length > 1) {
124+
return (
125+
<div className="markercolor-constantcontrols__container">
126+
<RadioBlocks
127+
options={constantOptions}
128+
activeOption={this.state.constantSelectedOption}
129+
onOptionChange={value =>
130+
this.setState({constantSelectedOption: value})
131+
}
132+
/>
133+
<Info>
134+
{this.state.constantSelectedOption === 'single'
135+
? _('All traces will be colored in the the same color.')
136+
: _(
137+
'Each trace will be colored according to the selected colorscale.'
138+
)}
139+
</Info>
140+
{this.state.constantSelectedOption === 'single' ? (
141+
<Color
142+
attr="marker.color"
143+
updatePlot={this.setValue}
144+
fullValue={this.state.value.constant}
145+
/>
146+
) : (
147+
<Colorscale
148+
suppressMultiValuedMessage
149+
attr="marker.color"
150+
updatePlot={this.setColors}
151+
colorscale={this.state.colorscale}
152+
/>
153+
)}
154+
</div>
155+
);
156+
}
157+
158+
return (
159+
<Color
160+
attr="marker.color"
161+
updatePlot={this.setValue}
162+
fullValue={this.state.value.constant}
163+
/>
164+
);
165+
}
166+
167+
renderVariableControls() {
168+
const _ = this.context.localize;
169+
return (
170+
<Fragment>
171+
<DataSelector
172+
attr="marker.color"
173+
placeholder={_('Select a Data Option')}
174+
/>
175+
{this.props.container.marker &&
176+
this.props.container.marker.colorscale === MULTI_VALUED ? null : (
177+
<Colorscale
178+
attr="marker.colorscale"
179+
updatePlot={this.setColorScale}
180+
colorscale={this.state.colorscale}
181+
/>
182+
)}
183+
</Fragment>
184+
);
185+
}
186+
80187
render() {
81-
const {attr, fullValue, container} = this.props;
188+
const {attr} = this.props;
82189
const {localize: _} = this.context;
83-
const {type, value, colorscale} = this.state;
190+
const {type} = this.state;
84191
const options = [
85192
{label: _('Constant'), value: 'constant'},
86193
{label: _('Variable'), value: 'variable'},
87194
];
88-
const multiValued =
89-
this.props.multiValued ||
90-
(Array.isArray(fullValue) && fullValue.includes(MULTI_VALUED)) ||
91-
(container.marker && container.marker.colorscale === MULTI_VALUED) ||
92-
(container.marker && container.marker.colorsrc === MULTI_VALUED) ||
93-
(container.marker &&
94-
container.marker.color &&
95-
Array.isArray(container.marker.color) &&
96-
container.marker.color.includes(MULTI_VALUED));
97195

98196
return (
99197
<Fragment>
100-
<Field {...this.props} multiValued={multiValued} attr={attr}>
198+
<Field {...this.props} multiValued={this.isMultiValued()} attr={attr}>
101199
<RadioBlocks
102200
options={options}
103201
activeOption={type}
104202
onOptionChange={this.setType}
105203
/>
106-
{!type ? null : type === 'constant' ? (
107-
<Color
108-
suppressMultiValuedMessage
109-
attr="marker.color"
110-
updatePlot={this.setValue}
111-
fullValue={value.constant}
112-
/>
113-
) : container.marker &&
114-
container.marker.colorsrc === MULTI_VALUED ? null : (
115-
<Fragment>
116-
<DataSelector suppressMultiValuedMessage attr="marker.color" />
117-
{container.marker &&
118-
container.marker.colorscale === MULTI_VALUED ? null : (
119-
<Colorscale
120-
suppressMultiValuedMessage
121-
attr="marker.colorscale"
122-
updatePlot={this.setColorScale}
123-
colorscale={colorscale}
124-
/>
125-
)}
126-
</Fragment>
204+
205+
{!type ? null : (
206+
<Info>
207+
{type === 'constant'
208+
? _('All points in a trace are colored in the same color.')
209+
: _('Each point in a trace is colored according to data.')}
210+
</Info>
127211
)}
212+
213+
{!type
214+
? null
215+
: type === 'constant'
216+
? this.renderConstantControls()
217+
: this.renderVariableControls()}
128218
</Field>
129-
{type === 'constant' ? (
130-
''
131-
) : (
219+
{type === 'constant' ? null : (
132220
<Fragment>
133221
<Radio
134222
label={_('Colorscale Direction')}
@@ -175,6 +263,7 @@ UnconnectedMarkerColor.propTypes = {
175263
UnconnectedMarkerColor.contextTypes = {
176264
localize: PropTypes.func,
177265
updateContainer: PropTypes.func,
266+
traceIndexes: PropTypes.array,
178267
};
179268

180269
export default connectToContainer(UnconnectedMarkerColor);

src/components/widgets/ColorscalePicker.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,4 @@ Scale.contextTypes = {
8888
localize: PropTypes.func,
8989
};
9090

91-
Scale.contextTypes = {
92-
localize: PropTypes.func,
93-
};
94-
9591
export default Scale;

src/default_panels/StyleTracesPanel.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,11 @@ const StyleTracesPanel = (props, {localize: _}) => (
225225
/>
226226
<NumericFraction label={_('Jitter')} attr="jitter" />
227227
<Numeric label={_('Position')} attr="pointpos" step={0.1} showSlider />
228-
<MarkerColor label={_('Color')} attr="marker.color" />
228+
<MarkerColor
229+
suppressMultiValuedMessage
230+
label={_('Color')}
231+
attr="marker.color"
232+
/>
229233
<NumericFraction label={_('Opacity')} attr="marker.opacity" />
230234
<MarkerSize label={_('Size')} attr="marker.size" />
231235
<Radio

src/lib/connectToContainer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const containerConnectedContextTypes = {
1717
onUpdate: PropTypes.func,
1818
plotly: PropTypes.object,
1919
updateContainer: PropTypes.func,
20+
traceIndexes: PropTypes.array,
2021
};
2122

2223
export default function connectToContainer(WrappedComponent, config = {}) {

src/lib/connectTraceToPlot.js

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export default function connectTraceToPlot(WrappedComponent) {
7272
deleteContainer: this.deleteTrace,
7373
container: trace,
7474
fullContainer: fullTrace,
75+
traceIndexes: this.props.traceIndexes,
7576
};
7677

7778
if (traceIndexes.length > 1) {
@@ -108,13 +109,25 @@ export default function connectTraceToPlot(WrappedComponent) {
108109

109110
updateTrace(update) {
110111
if (this.context.onUpdate) {
111-
this.context.onUpdate({
112-
type: EDITOR_ACTIONS.UPDATE_TRACES,
113-
payload: {
114-
update,
115-
traceIndexes: this.props.traceIndexes,
116-
},
117-
});
112+
if (Array.isArray(update)) {
113+
update.forEach((u, i) => {
114+
this.context.onUpdate({
115+
type: EDITOR_ACTIONS.UPDATE_TRACES,
116+
payload: {
117+
update: u,
118+
traceIndexes: [this.props.traceIndexes[i]],
119+
},
120+
});
121+
});
122+
} else {
123+
this.context.onUpdate({
124+
type: EDITOR_ACTIONS.UPDATE_TRACES,
125+
payload: {
126+
update,
127+
traceIndexes: this.props.traceIndexes,
128+
},
129+
});
130+
}
118131
}
119132
}
120133

@@ -156,6 +169,7 @@ export default function connectTraceToPlot(WrappedComponent) {
156169
defaultContainer: PropTypes.object,
157170
container: PropTypes.object,
158171
fullContainer: PropTypes.object,
172+
traceIndexes: PropTypes.array,
159173
};
160174

161175
const {plotly_editor_traits} = WrappedComponent;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
@import 'field';
22
@import 'symbolselector';
3+
@import 'markercolor';
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.markercolor-constantcontrols__container {
2+
margin-top: var(--spacing-quarter-unit);
3+
}

src/styles/components/widgets/_colorpicker.scss

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ $slider-picker-height: 10px;
2222
padding: var(--spacing-eighth-unit);
2323

2424
&__popover {
25-
left: -90px;
25+
left: -60px;
2626
position: absolute;
2727
top: 100%;
2828
margin: var(--spacing-quarter-unit) 0 var(--spacing-base-unit);
@@ -71,13 +71,13 @@ $slider-picker-height: 10px;
7171
}
7272
&__custom-input {
7373
padding: var(--spacing-quarter-unit) var(--spacing-half-unit);
74-
input{
74+
input {
7575
border: var(--border-default) !important;
7676
box-shadow: none !important;
7777
background-color: var(--color-background-inputs);
7878
color: var(--color-text-dark);
7979
}
80-
input + span{
80+
input + span {
8181
color: var(--color-text) !important;
8282
}
8383
}
@@ -117,9 +117,9 @@ $slider-picker-height: 10px;
117117

118118
&__preset-colors {
119119
margin: 0 var(--spacing-half-unit);
120-
& > div{
120+
& > div {
121121
border-top: var(--border-light) !important; // inline style override
122-
padding:var(--spacing-half-unit) !important;
122+
padding: var(--spacing-half-unit) !important;
123123
padding-bottom: var(--spacing-quarter-unit) !important;
124124
}
125125
}

src/styles/components/widgets/_colorscalepicker.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,5 @@
2424
}
2525

2626
.customPickerContainer {
27-
margin-top: 10px;
27+
margin-top: var(--spacing-quarter-unit);
2828
}

0 commit comments

Comments
 (0)