Skip to content

Commit 975ea1b

Browse files
authored
Merge pull request #610 from plotly/subplots
subplots panel initial
2 parents d0dbdd6 + 480f766 commit 975ea1b

29 files changed

+1257
-156
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
"react-dropzone": "^4.2.9",
2424
"react-plotly.js": "^2.2.0",
2525
"react-rangeslider": "^2.2.0",
26+
"react-resizable-rotatable-draggable": "^0.1.8",
2627
"react-select": "^1.2.0",
2728
"react-tabs": "^2.2.1",
29+
"styled-components": "^3.3.3",
2830
"tinycolor2": "^1.4.1"
2931
},
3032
"devDependencies": {

src/DefaultEditor.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {PanelMenuWrapper} from './components';
44
import {
55
GraphCreatePanel,
66
GraphTransformsPanel,
7+
GraphSubplotsPanel,
78
StyleLayoutPanel,
89
StyleAxesPanel,
910
StyleLegendPanel,
@@ -26,6 +27,7 @@ class DefaultEditor extends Component {
2627
<PanelMenuWrapper>
2728
{logo ? logo : null}
2829
<GraphCreatePanel group={_('Graph')} name={_('Create')} />
30+
<GraphSubplotsPanel group={_('Graph')} name={_('Subplots')} />
2931
<GraphTransformsPanel group={_('Graph')} name={_('Transforms')} />
3032
<StyleTracesPanel group={_('Style')} name={_('Traces')} />
3133
<StyleLayoutPanel group={_('Style')} name={_('Layout')} />

src/EditorControls.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ class EditorControls extends Component {
149149
if (this.props.beforeDeleteTrace) {
150150
this.props.beforeDeleteTrace(payload);
151151
}
152+
153+
shamefullyAdjustAxisRef(graphDiv, payload);
154+
152155
graphDiv.data.splice(payload.traceIndexes[0], 1);
153156
if (this.props.afterDeleteTrace) {
154157
this.props.afterDeleteTrace(payload);

src/components/containers/PlotlyPanel.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,10 @@ export class Panel extends Component {
6868
let numFolds = 0;
6969

7070
React.Children.forEach(this.props.children, child => {
71-
if ((child.type.plotly_editor_traits || {}).foldable) {
71+
if (
72+
((child && child.type && child.type.plotly_editor_traits) || {})
73+
.foldable
74+
) {
7275
numFolds++;
7376
}
7477
});
@@ -100,7 +103,10 @@ export class Panel extends Component {
100103
const newChildren = React.Children.map(
101104
this.props.children,
102105
(child, index) => {
103-
if ((child.type.plotly_editor_traits || {}).foldable) {
106+
if (
107+
((child && child.type && child.type.plotly_editor_traits) || {})
108+
.foldable
109+
) {
104110
return cloneElement(child, {
105111
key: index,
106112
folded: individualFoldStates[index] || false,
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import PlotlyFold from './PlotlyFold';
2+
import TraceRequiredPanel from './TraceRequiredPanel';
3+
import PropTypes from 'prop-types';
4+
import React, {Component} from 'react';
5+
import {
6+
connectTraceToPlot,
7+
connectCartesianSubplotToLayout,
8+
connectNonCartesianSubplotToLayout,
9+
} from 'lib';
10+
import {TRACE_TO_AXIS, SUBPLOT_TO_ATTR} from 'lib/constants';
11+
12+
const TraceFold = connectTraceToPlot(PlotlyFold);
13+
const NonCartesianSubplotFold = connectNonCartesianSubplotToLayout(PlotlyFold);
14+
const CartesianSubplotFold = connectCartesianSubplotToLayout(PlotlyFold);
15+
16+
class SubplotAccordion extends Component {
17+
render() {
18+
const {data = [], layout = {}} = this.context;
19+
const {children, messageIfEmptyFold} = this.props;
20+
const subplotFolds = [];
21+
22+
const allCartesianAxisCombinations = data.reduce((acc, curVal, inx) => {
23+
if (TRACE_TO_AXIS.cartesian.some(c => c === curVal.type)) {
24+
const xaxis = 'xaxis' + (curVal.xaxis ? curVal.xaxis.substring(1) : '');
25+
const yaxis = 'yaxis' + (curVal.yaxis ? curVal.yaxis.substring(1) : '');
26+
27+
const existingComboIndex = acc.findIndex(
28+
t => t.xaxis === xaxis && t.yaxis === yaxis
29+
);
30+
if (existingComboIndex === -1) {
31+
acc.push({
32+
xaxis: xaxis,
33+
yaxis: yaxis,
34+
xaxisName: curVal.xaxis || 'x1',
35+
yaxisName: curVal.yaxis || 'y1',
36+
index: [inx],
37+
});
38+
} else {
39+
acc[existingComboIndex].index.push(inx);
40+
}
41+
}
42+
return acc;
43+
}, []);
44+
45+
allCartesianAxisCombinations.forEach(
46+
d =>
47+
(subplotFolds[d.index[0]] = (
48+
<CartesianSubplotFold
49+
key={d.index[0]}
50+
traceIndexes={d.index}
51+
canDelete={false}
52+
messageIfEmpty={messageIfEmptyFold}
53+
xaxis={d.xaxis}
54+
yaxis={d.yaxis}
55+
name={`${d.xaxisName} ${d.yaxisName}`}
56+
>
57+
{children}
58+
</CartesianSubplotFold>
59+
))
60+
);
61+
62+
// For each key in layout, find all traces that belong to this subplot.
63+
// E.g. if layout attr is 'ternary', find all traces that are of type
64+
// that has subplot ternary, if layout attr is 'ternary2', find traces
65+
// of right type that have attr 'subplot': 'ternary' in their data.
66+
67+
/**
68+
Example:
69+
{
70+
"data": [
71+
{
72+
"type": "scatterternary",
73+
"mode": "markers",
74+
},
75+
{
76+
"type": "scatterternary",
77+
"mode": "markers",
78+
"subplot": "ternary2"
79+
}
80+
],
81+
"layout": {
82+
"ternary": {},
83+
"ternary2": {},
84+
},
85+
}
86+
*/
87+
88+
Object.keys(layout).forEach(layoutKey => {
89+
const traceIndexes = [];
90+
if (
91+
['geo', 'mapbox', 'polar', 'gl3d', 'ternary'].some(subplotType => {
92+
const trIndex =
93+
SUBPLOT_TO_ATTR[subplotType].layout === layoutKey
94+
? data.findIndex(trace =>
95+
TRACE_TO_AXIS[subplotType].some(tt => tt === trace.type)
96+
)
97+
: data.findIndex(
98+
trace =>
99+
trace[SUBPLOT_TO_ATTR[subplotType].data] === layoutKey
100+
);
101+
if (trIndex !== -1) {
102+
traceIndexes.push(trIndex);
103+
}
104+
return layoutKey.startsWith(SUBPLOT_TO_ATTR[subplotType].layout);
105+
})
106+
) {
107+
subplotFolds[traceIndexes[0]] = (
108+
<NonCartesianSubplotFold
109+
key={layoutKey}
110+
traceIndexes={traceIndexes}
111+
canDelete={false}
112+
messageIfEmpty={messageIfEmptyFold}
113+
subplot={layoutKey}
114+
name={layoutKey}
115+
>
116+
{children}
117+
</NonCartesianSubplotFold>
118+
);
119+
}
120+
});
121+
122+
data.forEach((d, i) => {
123+
if ((d.type === 'pie' && d.values) || d.type === 'table') {
124+
subplotFolds[i] = (
125+
<TraceFold
126+
key={i}
127+
traceIndexes={[i]}
128+
canDelete={false}
129+
messageIfEmpty={messageIfEmptyFold}
130+
name={`${d.type} ${i}`}
131+
>
132+
{children}
133+
</TraceFold>
134+
);
135+
}
136+
});
137+
138+
return <TraceRequiredPanel>{subplotFolds}</TraceRequiredPanel>;
139+
}
140+
}
141+
142+
SubplotAccordion.contextTypes = {
143+
fullData: PropTypes.array,
144+
data: PropTypes.array,
145+
layout: PropTypes.object,
146+
localize: PropTypes.func,
147+
};
148+
149+
SubplotAccordion.propTypes = {
150+
children: PropTypes.node,
151+
messageIfEmptyFold: PropTypes.string,
152+
};
153+
154+
export default SubplotAccordion;

src/components/containers/__tests__/Fold-test.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ describe('<PlotlyFold>', () => {
4646
.simulate('click');
4747

4848
const payload = beforeDeleteTrace.mock.calls[0][0];
49-
expect(payload).toEqual({traceIndexes: [0]});
49+
expect(payload).toEqual({
50+
axesToBeGarbageCollected: [],
51+
subplotToBeGarbageCollected: null,
52+
traceIndexes: [0],
53+
});
5054
});
5155
});

src/components/containers/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import PlotlyFold, {Fold} from './PlotlyFold';
99
import MenuPanel from './MenuPanel';
1010
import PlotlyPanel, {Panel} from './PlotlyPanel';
1111
import PlotlySection, {Section} from './PlotlySection';
12+
import SubplotAccordion from './SubplotAccordion';
1213
import TraceAccordion from './TraceAccordion';
1314
import TransformAccordion from './TransformAccordion';
1415
import TraceMarkerSection from './TraceMarkerSection';
@@ -32,6 +33,7 @@ export {
3233
Panel,
3334
PlotlySection,
3435
Section,
36+
SubplotAccordion,
3537
TraceAccordion,
3638
TransformAccordion,
3739
TraceMarkerSection,

0 commit comments

Comments
 (0)