Skip to content

Commit 142d86c

Browse files
authored
Merge pull request #630 from plotly/color-splits
Style splits
2 parents 12d2bab + 6dab0a4 commit 142d86c

File tree

11 files changed

+419
-176
lines changed

11 files changed

+419
-176
lines changed

src/EditorControls.js

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
shamefullyAdjustAxisRef,
88
shamefullyAdjustGeo,
99
shamefullyAddTableColumns,
10+
shamefullyCreateSplitStyleProps,
11+
shamefullyAdjustSplitStyleTargetContainers,
1012
} from './shame';
1113
import {EDITOR_ACTIONS} from './lib/constants';
1214
import isNumeric from 'fast-isnumeric';
@@ -66,16 +68,32 @@ class EditorControls extends Component {
6668
shamefullyClearAxisTypes(graphDiv, payload);
6769
shamefullyAdjustAxisRef(graphDiv, payload);
6870
shamefullyAddTableColumns(graphDiv, payload);
71+
shamefullyAdjustSplitStyleTargetContainers(graphDiv, payload);
6972

7073
for (let i = 0; i < payload.traceIndexes.length; i++) {
7174
for (const attr in payload.update) {
7275
const traceIndex = payload.traceIndexes[i];
73-
const prop = nestedProperty(graphDiv.data[traceIndex], attr);
76+
const splitTraceGroup = payload.splitTraceGroup
77+
? payload.splitTraceGroup.toString()
78+
: null;
79+
80+
let props = [nestedProperty(graphDiv.data[traceIndex], attr)];
7481
const value = payload.update[attr];
7582

76-
if (value !== void 0) {
77-
prop.set(value);
83+
if (splitTraceGroup) {
84+
props = shamefullyCreateSplitStyleProps(
85+
graphDiv,
86+
attr,
87+
traceIndex,
88+
splitTraceGroup
89+
);
7890
}
91+
92+
props.forEach(p => {
93+
if (value !== void 0) {
94+
p.set(value);
95+
}
96+
});
7997
}
8098
}
8199

src/components/containers/TraceAccordion.js

Lines changed: 126 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -10,38 +10,109 @@ import {Tab, Tabs, TabList, TabPanel} from 'react-tabs';
1010
const TraceFold = connectTraceToPlot(PlotlyFold);
1111

1212
class TraceAccordion extends Component {
13-
render() {
14-
const {data = [], localize: _} = this.context;
15-
const {
16-
canAdd,
17-
canGroup,
18-
children,
19-
messageIfEmptyFold,
20-
excludeFits,
21-
} = this.props;
13+
constructor(props, context) {
14+
super(props, context);
15+
this.setLocals(props, context);
16+
}
2217

18+
componentWillReceiveProps(nextProps, nextContext) {
19+
this.setLocals(nextProps, nextContext);
20+
}
21+
22+
setLocals(props, context) {
2323
// we don't want to include analysis transforms when we're in the create panel
24-
const filteredData = data.filter(t => {
25-
if (excludeFits) {
24+
const base = props.canGroup ? context.fullData : context.data;
25+
26+
this.filteredTracesFullDataPositions = [];
27+
this.filteredTraces = base.filter((t, i) => {
28+
if (props.excludeFits) {
2629
return !(t.transforms && t.transforms.every(tr => tr.type === 'fit'));
2730
}
31+
this.filteredTracesFullDataPositions.push(i);
2832
return true;
2933
});
34+
}
3035

31-
const individualTraces =
32-
filteredData.length &&
33-
filteredData.map((d, i) => {
34-
return (
35-
<TraceFold
36-
key={i}
37-
traceIndexes={[i]}
38-
canDelete={canAdd}
39-
messageIfEmpty={messageIfEmptyFold}
40-
>
41-
{children}
42-
</TraceFold>
43-
);
44-
});
36+
renderGroupedTraceFolds() {
37+
if (!this.filteredTraces.length || this.filteredTraces.length < 2) {
38+
return null;
39+
}
40+
41+
const dataArrayPositionsByTraceType = {};
42+
const fullDataArrayPositionsByTraceType = {};
43+
44+
this.filteredTraces.forEach((trace, index) => {
45+
const traceType = plotlyTraceToCustomTrace(trace);
46+
if (!dataArrayPositionsByTraceType[traceType]) {
47+
dataArrayPositionsByTraceType[traceType] = [];
48+
}
49+
50+
if (!fullDataArrayPositionsByTraceType[traceType]) {
51+
fullDataArrayPositionsByTraceType[traceType] = [];
52+
}
53+
54+
dataArrayPositionsByTraceType[traceType].push(trace.index);
55+
fullDataArrayPositionsByTraceType[traceType].push(
56+
this.filteredTracesFullDataPositions[index]
57+
);
58+
});
59+
60+
return Object.keys(fullDataArrayPositionsByTraceType).map((type, index) => {
61+
return (
62+
<TraceFold
63+
key={index}
64+
traceIndexes={dataArrayPositionsByTraceType[type]}
65+
name={type}
66+
fullDataArrayPosition={fullDataArrayPositionsByTraceType[type]}
67+
>
68+
{this.props.children}
69+
</TraceFold>
70+
);
71+
});
72+
}
73+
74+
renderUngroupedTraceFolds() {
75+
if (!this.filteredTraces.length) {
76+
return null;
77+
}
78+
79+
return this.filteredTraces.map((d, i) => {
80+
return (
81+
<TraceFold
82+
key={i}
83+
traceIndexes={[d.index]}
84+
canDelete={this.props.canAdd}
85+
messageIfEmpty={this.props.messageIfEmptyFold}
86+
fullDataArrayPosition={[this.filteredTracesFullDataPositions[i]]}
87+
>
88+
{this.props.children}
89+
</TraceFold>
90+
);
91+
});
92+
}
93+
94+
renderTraceFolds() {
95+
if (!this.filteredTraces.length) {
96+
return null;
97+
}
98+
99+
return this.filteredTraces.map((d, i) => {
100+
return (
101+
<TraceFold
102+
key={i}
103+
traceIndexes={[i]}
104+
canDelete={this.props.canAdd}
105+
messageIfEmpty={this.props.messageIfEmptyFold}
106+
>
107+
{this.props.children}
108+
</TraceFold>
109+
);
110+
});
111+
}
112+
113+
render() {
114+
const {canAdd, canGroup} = this.props;
115+
const _ = this.context.localize;
45116

46117
if (canAdd) {
47118
const addAction = {
@@ -54,58 +125,44 @@ class TraceAccordion extends Component {
54125
}
55126
},
56127
};
128+
57129
return (
58130
<PlotlyPanel addAction={addAction}>
59-
{individualTraces ? individualTraces : null}
131+
{this.renderTraceFolds()}
60132
</PlotlyPanel>
61133
);
62134
}
63-
const tracesByGroup = filteredData.reduce((allTraces, nextTrace, index) => {
64-
const traceType = plotlyTraceToCustomTrace(nextTrace);
65-
if (!allTraces[traceType]) {
66-
allTraces[traceType] = [];
67-
}
68-
allTraces[traceType].push(index);
69-
return allTraces;
70-
}, {});
71135

72-
const groupedTraces = Object.keys(tracesByGroup).map((traceType, index) => {
73-
return (
74-
<TraceFold
75-
key={index}
76-
traceIndexes={tracesByGroup[traceType]}
77-
name={traceType}
78-
>
79-
{this.props.children}
80-
</TraceFold>
81-
);
82-
});
136+
if (canGroup) {
137+
if (this.filteredTraces.length === 1) {
138+
return (
139+
<TraceRequiredPanel>
140+
{this.renderUngroupedTraceFolds()}
141+
</TraceRequiredPanel>
142+
);
143+
}
83144

84-
if (canGroup && filteredData.length > 1 && groupedTraces.length > 0) {
85-
return (
86-
<TraceRequiredPanel noPadding>
87-
<Tabs>
88-
<TabList>
89-
<Tab>{_('Individually')}</Tab>
90-
<Tab>{_('By Type')}</Tab>
91-
</TabList>
92-
<TabPanel>
93-
<PlotlyPanel>
94-
{individualTraces ? individualTraces : null}
95-
</PlotlyPanel>
96-
</TabPanel>
97-
<TabPanel>
98-
<PlotlyPanel>{groupedTraces ? groupedTraces : null}</PlotlyPanel>
99-
</TabPanel>
100-
</Tabs>
101-
</TraceRequiredPanel>
102-
);
145+
if (this.filteredTraces.length > 1) {
146+
return (
147+
<TraceRequiredPanel noPadding>
148+
<Tabs>
149+
<TabList>
150+
<Tab>{_('Individually')}</Tab>
151+
<Tab>{_('By Type')}</Tab>
152+
</TabList>
153+
<TabPanel>
154+
<PlotlyPanel>{this.renderUngroupedTraceFolds()}</PlotlyPanel>
155+
</TabPanel>
156+
<TabPanel>
157+
<PlotlyPanel>{this.renderGroupedTraceFolds()}</PlotlyPanel>
158+
</TabPanel>
159+
</Tabs>
160+
</TraceRequiredPanel>
161+
);
162+
}
103163
}
104-
return (
105-
<TraceRequiredPanel>
106-
{individualTraces ? individualTraces : null}
107-
</TraceRequiredPanel>
108-
);
164+
165+
return <TraceRequiredPanel>{this.renderTraceFolds()}</TraceRequiredPanel>;
109166
}
110167
}
111168

src/components/containers/TransformAccordion.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,17 @@ class TransformAccordion extends Component {
8383
</TransformFold>
8484
));
8585

86+
// cannot have 2 Split transforms on one trace:
87+
// https://github.com/plotly/plotly.js/issues/1742
88+
const addActionOptions =
89+
container.transforms &&
90+
container.transforms.some(t => t.type === 'groupby')
91+
? transformTypes.filter(t => t.type !== 'groupby')
92+
: transformTypes;
93+
8694
const addAction = {
8795
label: _('Transform'),
88-
handler: transformTypes.map(({label, type}) => {
96+
handler: addActionOptions.map(({label, type}) => {
8997
return {
9098
label,
9199
handler: context => {
@@ -104,6 +112,10 @@ class TransformAccordion extends Component {
104112
payload.groups = null;
105113
}
106114

115+
if (type === 'groupby') {
116+
payload.styles = [];
117+
}
118+
107119
updateContainer({[key]: payload});
108120
}
109121
},

src/components/fields/DataSelector.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class UnconnectedDataSelector extends Component {
4949
Array.isArray(this.fullValue);
5050
}
5151

52-
this.hasData = props.attr in props.container;
52+
this.hasData = props.container ? props.attr in props.container : false;
5353
}
5454

5555
updatePlot(value) {
@@ -83,6 +83,7 @@ export class UnconnectedDataSelector extends Component {
8383
: null,
8484
}
8585
);
86+
8687
this.props.updateContainer(update);
8788
}
8889

@@ -136,6 +137,7 @@ UnconnectedDataSelector.contextTypes = {
136137
toSrc: PropTypes.func.isRequired,
137138
fromSrc: PropTypes.func.isRequired,
138139
}),
140+
container: PropTypes.object,
139141
};
140142

141143
function modifyPlotProps(props, context, plotProps) {

0 commit comments

Comments
 (0)