Skip to content

Commit 6bc3c86

Browse files
rangeslider/rangeselector UI (#416)
1 parent c9733d7 commit 6bc3c86

22 files changed

+595
-44
lines changed

src/EditorControls.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,22 @@ class EditorControls extends Component {
223223
}
224224
break;
225225

226+
case EDITOR_ACTIONS.DELETE_RANGESELECTOR:
227+
if (isNumeric(payload.rangeselectorIndex)) {
228+
graphDiv.layout[payload.axisId].rangeselector.buttons.splice(
229+
payload.rangeselectorIndex,
230+
1
231+
);
232+
if (this.props.onUpdate) {
233+
this.props.onUpdate(
234+
graphDiv.data,
235+
Object.assign({}, graphDiv.layout),
236+
graphDiv._transitionData._frames
237+
);
238+
}
239+
}
240+
break;
241+
226242
default:
227243
throw new Error('must specify an action type to handleEditorUpdate');
228244
}

src/components/containers/PanelHeader.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ class PanelHeader extends Component {
5757

5858
PanelHeader.contextTypes = {
5959
layout: PropTypes.object,
60+
fullContainer: PropTypes.object,
6061
onUpdate: PropTypes.func,
6162
updateContainer: PropTypes.func,
6263
};
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Fold from './Fold';
2+
import Panel from './Panel';
3+
import PropTypes from 'prop-types';
4+
import React, {Component} from 'react';
5+
import {connectRangeSelectorToAxis, localize} from 'lib';
6+
7+
const RangeSelectorFold = connectRangeSelectorToAxis(Fold);
8+
9+
class RangeSelectorAccordion extends Component {
10+
render() {
11+
if (
12+
!this.context.fullContainer.rangeselector ||
13+
!this.context.fullContainer.rangeselector.visible ||
14+
// next line checks for "all" case
15+
this.context.fullContainer._axisGroup === 0
16+
) {
17+
return null;
18+
}
19+
20+
const {fullContainer: {rangeselector: {buttons = []}}} = this.context;
21+
const {children, localize: _} = this.props;
22+
23+
const content =
24+
buttons.length &&
25+
buttons.map((btn, i) => (
26+
<RangeSelectorFold
27+
key={i}
28+
rangeselectorIndex={i}
29+
buttonIndex={i}
30+
name={btn.label}
31+
canDelete={true}
32+
>
33+
{children}
34+
</RangeSelectorFold>
35+
));
36+
37+
const addAction = {
38+
label: _('Button'),
39+
handler: context => {
40+
const {fullContainer, updateContainer} = context;
41+
if (updateContainer) {
42+
const shapeIndex = Array.isArray(fullContainer.rangeselector.buttons)
43+
? fullContainer.rangeselector.buttons.length
44+
: 0;
45+
46+
updateContainer({[`rangeselector.buttons[${shapeIndex}]`]: {}});
47+
}
48+
},
49+
};
50+
51+
return <Panel addAction={addAction}>{content ? content : null}</Panel>;
52+
}
53+
}
54+
55+
RangeSelectorAccordion.contextTypes = {
56+
fullContainer: PropTypes.object,
57+
};
58+
59+
RangeSelectorAccordion.propTypes = {
60+
children: PropTypes.node,
61+
localize: PropTypes.func,
62+
};
63+
64+
export default localize(RangeSelectorAccordion);

src/components/containers/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ShapeAccordion from './ShapeAccordion';
33
import SliderAccordion from './SliderAccordion';
44
import ImageAccordion from './ImageAccordion';
55
import UpdateMenuAccordion from './UpdateMenuAccordion';
6+
import RangeSelectorAccordion from './RangeSelectorAccordion';
67
import AxesFold from './AxesFold';
78
import Fold from './Fold';
89
import MenuPanel from './MenuPanel';
@@ -23,6 +24,7 @@ export {
2324
SliderAccordion,
2425
ImageAccordion,
2526
UpdateMenuAccordion,
27+
RangeSelectorAccordion,
2628
MenuPanel,
2729
Fold,
2830
Panel,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import Field from './Field';
2+
import {UnconnectedNumeric} from './Numeric';
3+
import {UnconnectedText} from './Text';
4+
import PropTypes from 'prop-types';
5+
import React, {Component} from 'react';
6+
import {connectToContainer} from 'lib';
7+
8+
export class UnconnectedAxisRangeValue extends Component {
9+
render() {
10+
return this.props.multiValued ||
11+
(this.props.fullContainer && this.props.fullContainer.type === 'date') ? (
12+
<UnconnectedText {...this.props} />
13+
) : (
14+
<UnconnectedNumeric {...this.props} />
15+
);
16+
}
17+
}
18+
19+
UnconnectedAxisRangeValue.propTypes = {
20+
defaultValue: PropTypes.any,
21+
fullValue: PropTypes.any,
22+
min: PropTypes.number,
23+
max: PropTypes.number,
24+
multiValued: PropTypes.bool,
25+
hideArrows: PropTypes.bool,
26+
showSlider: PropTypes.bool,
27+
step: PropTypes.number,
28+
fullContainer: PropTypes.object,
29+
updatePlot: PropTypes.func,
30+
...Field.propTypes,
31+
};
32+
33+
export default connectToContainer(UnconnectedAxisRangeValue);

src/components/fields/Numeric.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class UnconnectedNumeric extends Component {
3333
}
3434

3535
UnconnectedNumeric.propTypes = {
36-
defaultValue: PropTypes.number,
36+
defaultValue: PropTypes.any,
3737
fullValue: PropTypes.any,
3838
min: PropTypes.number,
3939
max: PropTypes.number,

src/components/fields/Text.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import Field from './Field';
2+
import TextInput from '../widgets/TextInput';
3+
import PropTypes from 'prop-types';
4+
import React, {Component} from 'react';
5+
import {connectToContainer} from 'lib';
6+
7+
export class UnconnectedText extends Component {
8+
render() {
9+
let fullValue = this.props.fullValue;
10+
let placeholder;
11+
if (this.props.multiValued) {
12+
placeholder = fullValue;
13+
fullValue = '';
14+
}
15+
16+
return (
17+
<Field {...this.props}>
18+
<TextInput
19+
value={fullValue}
20+
defaultValue={this.props.defaultValue}
21+
placeholder={placeholder}
22+
onUpdate={this.props.updatePlot}
23+
/>
24+
</Field>
25+
);
26+
}
27+
}
28+
29+
UnconnectedText.propTypes = {
30+
defaultValue: PropTypes.any,
31+
fullValue: PropTypes.any,
32+
multiValued: PropTypes.bool,
33+
updatePlot: PropTypes.func,
34+
...Field.propTypes,
35+
};
36+
37+
export default connectToContainer(UnconnectedText);

src/components/fields/TraceSelector.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class TraceSelector extends Component {
6868
container &&
6969
container.uid &&
7070
!container.mode &&
71+
fullContainer._fullInput &&
7172
fullContainer._fullInput.type === 'scatter'
7273
) {
7374
updateContainer({

src/components/fields/derived.js

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import isNumeric from 'fast-isnumeric';
22
import {UnconnectedDropdown} from './Dropdown';
33
import {UnconnectedFlaglist} from './Flaglist';
44
import {UnconnectedNumeric} from './Numeric';
5+
import {UnconnectedAxisRangeValue} from './AxisRangeValue';
56
import {UnconnectedRadio} from './Radio';
67
import {
78
connectLayoutToPlot,
@@ -78,6 +79,33 @@ export const AxisOverlayDropdown = connectToContainer(UnconnectedDropdown, {
7879
},
7980
});
8081

82+
export const RangesliderVisible = connectToContainer(UnconnectedRadio, {
83+
modifyPlotProps: (props, context, plotProps) => {
84+
if (
85+
!plotProps.isVisible &&
86+
context.fullContainer._id &&
87+
context.fullContainer._id.startsWith('x')
88+
) {
89+
plotProps.isVisible = true;
90+
return;
91+
}
92+
},
93+
});
94+
95+
export const RangeselectorVisible = connectToContainer(UnconnectedRadio, {
96+
modifyPlotProps: (props, context, plotProps) => {
97+
if (
98+
!plotProps.isVisible &&
99+
context.fullContainer._id &&
100+
context.fullContainer._id.startsWith('x') &&
101+
context.fullContainer.type === 'date'
102+
) {
103+
plotProps.isVisible = true;
104+
return;
105+
}
106+
},
107+
});
108+
81109
export const AxisSide = connectToContainer(UnconnectedRadio, {
82110
modifyPlotProps: (props, context, plotProps) => {
83111
const _ = props.localize;
@@ -197,7 +225,7 @@ export const TraceOrientation = connectToContainer(UnconnectedRadio, {
197225
},
198226
});
199227

200-
export const AxesRange = connectToContainer(UnconnectedNumeric, {
228+
export const AxesRange = connectToContainer(UnconnectedAxisRangeValue, {
201229
modifyPlotProps: (props, context, plotProps) => {
202230
const {fullContainer} = plotProps;
203231
if (plotProps.isVisible && fullContainer && fullContainer.autorange) {
@@ -207,6 +235,34 @@ export const AxesRange = connectToContainer(UnconnectedNumeric, {
207235
},
208236
});
209237

238+
export const NTicks = connectToContainer(UnconnectedNumeric, {
239+
modifyPlotProps: (props, context, plotProps) => {
240+
const {fullContainer} = plotProps;
241+
if (
242+
plotProps.isVisible &&
243+
fullContainer &&
244+
fullContainer.tickmode !== 'auto'
245+
) {
246+
plotProps.isVisible = false;
247+
}
248+
return plotProps;
249+
},
250+
});
251+
252+
export const DTicks = connectToContainer(UnconnectedAxisRangeValue, {
253+
modifyPlotProps: (props, context, plotProps) => {
254+
const {fullContainer} = plotProps;
255+
if (
256+
plotProps.isVisible &&
257+
fullContainer &&
258+
fullContainer.tickmode !== 'linear'
259+
) {
260+
plotProps.isVisible = false;
261+
}
262+
return plotProps;
263+
},
264+
});
265+
210266
class UnconnectedNumericFraction extends UnconnectedNumeric {}
211267
UnconnectedNumericFraction.propTypes = UnconnectedNumeric.propTypes;
212268
UnconnectedNumericFraction.defaultProps = {

src/components/fields/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import Radio from './Radio';
1111
import TextEditor from './TextEditor';
1212
import DataSelector from './DataSelector';
1313
import Numeric from './Numeric';
14+
import AxisRangeValue from './AxisRangeValue';
15+
import Text from './Text';
1416
import SymbolSelector from './SymbolSelector';
1517
import TraceSelector from './TraceSelector';
1618
import ErrorBars from './ErrorBars';
@@ -21,6 +23,8 @@ import {
2123
AnnotationRef,
2224
PositioningRef,
2325
AxesRange,
26+
NTicks,
27+
DTicks,
2428
AxisAnchorDropdown,
2529
CanvasSize,
2630
ContourNumeric,
@@ -34,6 +38,8 @@ import {
3438
NumericFractionInverse,
3539
LayoutNumericFraction,
3640
LayoutNumericFractionInverse,
41+
RangesliderVisible,
42+
RangeselectorVisible,
3743
TraceOrientation,
3844
AxisOverlayDropdown,
3945
AxisSide,
@@ -47,6 +53,8 @@ export {
4753
PositioningRef,
4854
ArrowSelector,
4955
AxesRange,
56+
NTicks,
57+
DTicks,
5058
AxesSelector,
5159
CanvasSize,
5260
ColorPicker,
@@ -71,8 +79,12 @@ export {
7179
LineDashSelector,
7280
LineShapeSelector,
7381
Numeric,
82+
AxisRangeValue,
83+
Text,
7484
Radio,
7585
SymbolSelector,
86+
RangesliderVisible,
87+
RangeselectorVisible,
7688
TextEditor,
7789
TraceOrientation,
7890
TraceSelector,

src/components/index.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
PositioningRef,
88
ArrowSelector,
99
AxesRange,
10+
NTicks,
11+
DTicks,
1012
AxesSelector,
1113
CanvasSize,
1214
ColorPicker,
@@ -31,7 +33,11 @@ import {
3133
LineDashSelector,
3234
LineShapeSelector,
3335
Numeric,
36+
AxisRangeValue,
37+
Text,
3438
Radio,
39+
RangesliderVisible,
40+
RangeselectorVisible,
3541
AxisCreator,
3642
SymbolSelector,
3743
TextEditor,
@@ -47,6 +53,7 @@ import {
4753
SliderAccordion,
4854
ImageAccordion,
4955
UpdateMenuAccordion,
56+
RangeSelectorAccordion,
5057
AxesFold,
5158
Fold,
5259
LayoutPanel,
@@ -70,11 +77,16 @@ export {
7077
AxisAnchorDropdown,
7178
AxisOverlayDropdown,
7279
AxisSide,
80+
NTicks,
81+
DTicks,
7382
ShapeAccordion,
83+
RangeSelectorAccordion,
7484
SliderAccordion,
7585
ImageAccordion,
7686
UpdateMenuAccordion,
7787
AnnotationArrowRef,
88+
RangesliderVisible,
89+
RangeselectorVisible,
7890
AnnotationRef,
7991
PositioningRef,
8092
ArrowSelector,
@@ -108,6 +120,8 @@ export {
108120
LineShapeSelector,
109121
MenuPanel,
110122
Numeric,
123+
AxisRangeValue,
124+
Text,
111125
Panel,
112126
PanelMenuWrapper,
113127
Radio,

0 commit comments

Comments
 (0)