Skip to content
Merged
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
"react-dropzone": "^4.2.9",
"react-plotly.js": "^2.2.0",
"react-rangeslider": "^2.2.0",
"react-resizable-rotatable-draggable": "^0.1.8",
"react-select": "^1.2.0",
"react-tabs": "^2.2.1",
"styled-components": "^3.3.3",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are we actually using it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a peer dep of the draggy thing.

"tinycolor2": "^1.4.1"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions src/DefaultEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {PanelMenuWrapper} from './components';
import {
GraphCreatePanel,
GraphTransformsPanel,
GraphSubplotsPanel,
StyleLayoutPanel,
StyleAxesPanel,
StyleLegendPanel,
Expand All @@ -26,6 +27,7 @@ class DefaultEditor extends Component {
<PanelMenuWrapper>
{logo ? logo : null}
<GraphCreatePanel group={_('Graph')} name={_('Create')} />
<GraphSubplotsPanel group={_('Graph')} name={_('Subplots')} />
<GraphTransformsPanel group={_('Graph')} name={_('Transforms')} />
<StyleTracesPanel group={_('Style')} name={_('Traces')} />
<StyleLayoutPanel group={_('Style')} name={_('Layout')} />
Expand Down
3 changes: 3 additions & 0 deletions src/EditorControls.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ class EditorControls extends Component {
if (this.props.beforeDeleteTrace) {
this.props.beforeDeleteTrace(payload);
}

shamefullyAdjustAxisRef(graphDiv, payload);

graphDiv.data.splice(payload.traceIndexes[0], 1);
if (this.props.afterDeleteTrace) {
this.props.afterDeleteTrace(payload);
Expand Down
10 changes: 8 additions & 2 deletions src/components/containers/PlotlyPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,10 @@ export class Panel extends Component {
let numFolds = 0;

React.Children.forEach(this.props.children, child => {
if ((child.type.plotly_editor_traits || {}).foldable) {
if (
((child && child.type && child.type.plotly_editor_traits) || {})
.foldable
) {
numFolds++;
}
});
Expand Down Expand Up @@ -100,7 +103,10 @@ export class Panel extends Component {
const newChildren = React.Children.map(
this.props.children,
(child, index) => {
if ((child.type.plotly_editor_traits || {}).foldable) {
if (
((child && child.type && child.type.plotly_editor_traits) || {})
.foldable
) {
return cloneElement(child, {
key: index,
folded: individualFoldStates[index] || false,
Expand Down
154 changes: 154 additions & 0 deletions src/components/containers/SubplotAccordion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import PlotlyFold from './PlotlyFold';
import TraceRequiredPanel from './TraceRequiredPanel';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
import {
connectTraceToPlot,
connectCartesianSubplotToLayout,
connectNonCartesianSubplotToLayout,
} from 'lib';
import {TRACE_TO_AXIS, SUBPLOT_TO_ATTR} from 'lib/constants';

const TraceFold = connectTraceToPlot(PlotlyFold);
const NonCartesianSubplotFold = connectNonCartesianSubplotToLayout(PlotlyFold);
const CartesianSubplotFold = connectCartesianSubplotToLayout(PlotlyFold);

class SubplotAccordion extends Component {
render() {
const {data = [], layout = {}} = this.context;
const {children, messageIfEmptyFold} = this.props;
const subplotFolds = [];

const allCartesianAxisCombinations = data.reduce((acc, curVal, inx) => {
if (TRACE_TO_AXIS.cartesian.some(c => c === curVal.type)) {
const xaxis = 'xaxis' + (curVal.xaxis ? curVal.xaxis.substring(1) : '');
const yaxis = 'yaxis' + (curVal.yaxis ? curVal.yaxis.substring(1) : '');

const existingComboIndex = acc.findIndex(
t => t.xaxis === xaxis && t.yaxis === yaxis
);
if (existingComboIndex === -1) {
acc.push({
xaxis: xaxis,
yaxis: yaxis,
xaxisName: curVal.xaxis || 'x1',
yaxisName: curVal.yaxis || 'y1',
index: [inx],
});
} else {
acc[existingComboIndex].index.push(inx);
}
}
return acc;
}, []);

allCartesianAxisCombinations.forEach(
d =>
(subplotFolds[d.index[0]] = (
<CartesianSubplotFold
key={d.index[0]}
traceIndexes={d.index}
canDelete={false}
messageIfEmpty={messageIfEmptyFold}
xaxis={d.xaxis}
yaxis={d.yaxis}
name={`${d.xaxisName} ${d.yaxisName}`}
>
{children}
</CartesianSubplotFold>
))
);

// For each key in layout, find all traces that belong to this subplot.
// E.g. if layout attr is 'ternary', find all traces that are of type
// that has subplot ternary, if layout attr is 'ternary2', find traces
// of right type that have attr 'subplot': 'ternary' in their data.

/**
Example:
{
"data": [
{
"type": "scatterternary",
"mode": "markers",
},
{
"type": "scatterternary",
"mode": "markers",
"subplot": "ternary2"
}
],
"layout": {
"ternary": {},
"ternary2": {},
},
}
*/

Object.keys(layout).forEach(layoutKey => {
const traceIndexes = [];
if (
['geo', 'mapbox', 'polar', 'gl3d', 'ternary'].some(subplotType => {
const trIndex =
SUBPLOT_TO_ATTR[subplotType].layout === layoutKey
? data.findIndex(trace =>
TRACE_TO_AXIS[subplotType].some(tt => tt === trace.type)
)
: data.findIndex(
trace =>
trace[SUBPLOT_TO_ATTR[subplotType].data] === layoutKey
);
if (trIndex !== -1) {
traceIndexes.push(trIndex);
}
return layoutKey.startsWith(SUBPLOT_TO_ATTR[subplotType].layout);
})
) {
subplotFolds[traceIndexes[0]] = (
<NonCartesianSubplotFold
key={layoutKey}
traceIndexes={traceIndexes}
canDelete={false}
messageIfEmpty={messageIfEmptyFold}
subplot={layoutKey}
name={layoutKey}
>
{children}
</NonCartesianSubplotFold>
);
}
});

data.forEach((d, i) => {
if ((d.type === 'pie' && d.values) || d.type === 'table') {
subplotFolds[i] = (
<TraceFold
key={i}
traceIndexes={[i]}
canDelete={false}
messageIfEmpty={messageIfEmptyFold}
name={`${d.type} ${i}`}
>
{children}
</TraceFold>
);
}
});

return <TraceRequiredPanel>{subplotFolds}</TraceRequiredPanel>;
}
}

SubplotAccordion.contextTypes = {
fullData: PropTypes.array,
data: PropTypes.array,
layout: PropTypes.object,
localize: PropTypes.func,
};

SubplotAccordion.propTypes = {
children: PropTypes.node,
messageIfEmptyFold: PropTypes.string,
};

export default SubplotAccordion;
6 changes: 5 additions & 1 deletion src/components/containers/__tests__/Fold-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ describe('<PlotlyFold>', () => {
.simulate('click');

const payload = beforeDeleteTrace.mock.calls[0][0];
expect(payload).toEqual({traceIndexes: [0]});
expect(payload).toEqual({
axesToBeGarbageCollected: [],
subplotToBeGarbageCollected: null,
traceIndexes: [0],
});
});
});
2 changes: 2 additions & 0 deletions src/components/containers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import PlotlyFold, {Fold} from './PlotlyFold';
import MenuPanel from './MenuPanel';
import PlotlyPanel, {Panel} from './PlotlyPanel';
import PlotlySection, {Section} from './PlotlySection';
import SubplotAccordion from './SubplotAccordion';
import TraceAccordion from './TraceAccordion';
import TransformAccordion from './TransformAccordion';
import TraceMarkerSection from './TraceMarkerSection';
Expand All @@ -32,6 +33,7 @@ export {
Panel,
PlotlySection,
Section,
SubplotAccordion,
TraceAccordion,
TransformAccordion,
TraceMarkerSection,
Expand Down
Loading