diff --git a/package.json b/package.json
index 6756abf21..3319308fb 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"react-color": "^2.13.8",
"react-colorscales": "^0.4.2",
"react-dom": "^16.2.0",
+ "react-rangeslider": "^2.2.0",
"react-select": "^1.0.0-rc.10",
"react-tabs": "^2.2.1",
"tinycolor2": "^1.4.1"
diff --git a/src/components/fields/Numeric.js b/src/components/fields/Numeric.js
index 6dd952a1d..1ebd75392 100644
--- a/src/components/fields/Numeric.js
+++ b/src/components/fields/Numeric.js
@@ -25,6 +25,7 @@ export class UnconnectedNumeric extends Component {
onChange={this.props.updatePlot}
onUpdate={this.props.updatePlot}
showArrows={!this.props.hideArrows}
+ showSlider={this.props.showSlider}
/>
);
@@ -38,6 +39,7 @@ UnconnectedNumeric.propTypes = {
max: PropTypes.number,
multiValued: PropTypes.bool,
hideArrows: PropTypes.bool,
+ showSlider: PropTypes.bool,
step: PropTypes.number,
updatePlot: PropTypes.func,
...Field.propTypes,
diff --git a/src/components/fields/derived.js b/src/components/fields/derived.js
index 5adc0b9c6..59ce5dbf0 100644
--- a/src/components/fields/derived.js
+++ b/src/components/fields/derived.js
@@ -105,14 +105,45 @@ export const AxesRange = connectToContainer(UnconnectedNumeric, {
},
});
-class NumericFraction extends UnconnectedNumeric {}
-NumericFraction.propTypes = UnconnectedNumeric.propTypes;
-NumericFraction.defaultProps = {
+class UnconnectedNumericFraction extends UnconnectedNumeric {}
+UnconnectedNumericFraction.propTypes = UnconnectedNumeric.propTypes;
+UnconnectedNumericFraction.defaultProps = {
units: '%',
+ showSlider: true,
};
+const numericFractionModifyPlotProps = (props, context, plotProps) => {
+ const {attrMeta, fullValue, updatePlot} = plotProps;
+ const min = attrMeta.min || 0;
+ const max = attrMeta.max || 1;
+ if (isNumeric(fullValue)) {
+ plotProps.fullValue = Math.round(100 * (fullValue - min) / (max - min));
+ }
+
+ plotProps.updatePlot = v => {
+ if (isNumeric(v)) {
+ updatePlot(v / 100 * (max - min) + min);
+ } else {
+ updatePlot(v);
+ }
+ };
+ plotProps.max = 100;
+ plotProps.min = 0;
+};
+
+export const NumericFraction = connectToContainer(UnconnectedNumericFraction, {
+ modifyPlotProps: numericFractionModifyPlotProps,
+});
+
+export const LayoutNumericFraction = connectLayoutToPlot(
+ connectToContainer(UnconnectedNumericFraction, {
+ supplyPlotProps: supplyLayoutPlotProps,
+ modifyPlotProps: numericFractionModifyPlotProps,
+ })
+);
+
export const LayoutNumericFractionInverse = connectLayoutToPlot(
- connectToContainer(NumericFraction, {
+ connectToContainer(UnconnectedNumericFraction, {
supplyPlotProps: supplyLayoutPlotProps,
modifyPlotProps: (props, context, plotProps) => {
const {attrMeta, fullValue, updatePlot} = plotProps;
@@ -142,36 +173,6 @@ export const LayoutNumericFractionInverse = connectLayoutToPlot(
})
);
-export const LayoutNumericFraction = connectLayoutToPlot(
- connectToContainer(NumericFraction, {
- supplyPlotProps: supplyLayoutPlotProps,
- modifyPlotProps: (props, context, plotProps) => {
- const {attrMeta, fullValue, updatePlot} = plotProps;
- if (isNumeric(fullValue)) {
- plotProps.fullValue = fullValue * 100;
- }
-
- plotProps.updatePlot = v => {
- if (isNumeric(v)) {
- updatePlot(v / 100);
- } else {
- updatePlot(v);
- }
- };
-
- if (attrMeta) {
- if (isNumeric(attrMeta.max)) {
- plotProps.max = attrMeta.max * 100;
- }
-
- if (isNumeric(attrMeta.min)) {
- plotProps.min = attrMeta.min * 100;
- }
- }
- },
- })
-);
-
export const AnnotationArrowRef = connectToContainer(UnconnectedDropdown, {
modifyPlotProps: (props, context, plotProps) => {
const {fullContainer: {xref, yref}, plotly, graphDiv} = context;
@@ -271,6 +272,34 @@ export const PositioningRef = connectToContainer(UnconnectedDropdown, {
},
});
+export const PositioningNumeric = connectToContainer(UnconnectedNumeric, {
+ modifyPlotProps: (props, context, plotProps) => {
+ const {fullContainer, fullValue, updatePlot} = plotProps;
+ if (
+ fullContainer &&
+ (fullContainer[props.attr[0] + 'ref'] === 'paper' ||
+ fullContainer[props.attr[props.attr.length - 1] + 'ref'] === 'paper')
+ ) {
+ plotProps.units = '%';
+ plotProps.showSlider = true;
+ plotProps.max = 100;
+ plotProps.min = 0;
+ plotProps.step = 1;
+ if (isNumeric(fullValue)) {
+ plotProps.fullValue = Math.round(100 * fullValue);
+ }
+
+ plotProps.updatePlot = v => {
+ if (isNumeric(v)) {
+ updatePlot(v / 100);
+ } else {
+ updatePlot(v);
+ }
+ };
+ }
+ },
+});
+
function computeAxesRefOptions(axes) {
const options = [];
for (let i = 0; i < axes.length; i++) {
diff --git a/src/components/fields/index.js b/src/components/fields/index.js
index 47b910cfd..053742ba3 100644
--- a/src/components/fields/index.js
+++ b/src/components/fields/index.js
@@ -24,6 +24,9 @@ import {
GeoProjections,
GeoScope,
HoverInfo,
+ NumericFraction,
+ PositioningNumeric,
+ NumericFractionInverse,
LayoutNumericFraction,
LayoutNumericFractionInverse,
TraceOrientation,
@@ -53,6 +56,9 @@ export {
Info,
LayoutNumericFraction,
LayoutNumericFractionInverse,
+ NumericFraction,
+ NumericFractionInverse,
+ PositioningNumeric,
LineDashSelector,
LineShapeSelector,
Numeric,
diff --git a/src/components/index.js b/src/components/index.js
index 1d004da59..e950d48fc 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -21,6 +21,9 @@ import {
Info,
LayoutNumericFraction,
LayoutNumericFractionInverse,
+ NumericFraction,
+ PositioningNumeric,
+ NumericFractionInverse,
LineDashSelector,
LineShapeSelector,
Numeric,
@@ -81,6 +84,9 @@ export {
GeoScope,
HoverInfo,
Info,
+ NumericFraction,
+ PositioningNumeric,
+ NumericFractionInverse,
LayoutNumericFraction,
LayoutNumericFractionInverse,
LayoutPanel,
diff --git a/src/components/widgets/NumericInput.js b/src/components/widgets/NumericInput.js
index 6ae7ed70a..514925efb 100644
--- a/src/components/widgets/NumericInput.js
+++ b/src/components/widgets/NumericInput.js
@@ -2,6 +2,7 @@ import EditableText from './EditableText';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import isNumeric from 'fast-isnumeric';
+import Slider from 'react-rangeslider';
import {CarretDownIcon, CarretUpIcon} from 'plotly-icons';
export const UP_ARROW = 38;
@@ -94,7 +95,7 @@ export default class NumericInput extends Component {
}
renderArrows() {
- if (!this.props.showArrows) {
+ if (!this.props.showArrows || this.props.showSlider) {
return null;
}
@@ -116,6 +117,23 @@ export default class NumericInput extends Component {
);
}
+ renderSlider() {
+ if (!this.props.showSlider) {
+ return null;
+ }
+
+ return (
+
+ );
+ }
+
render() {
return (
@@ -129,6 +147,7 @@ export default class NumericInput extends Component {
onKeyDown={this.onKeyDown}
/>
{this.renderArrows()}
+ {this.renderSlider()}
);
}
@@ -143,6 +162,7 @@ NumericInput.propTypes = {
onUpdate: PropTypes.func.isRequired,
placeholder: PropTypes.string,
showArrows: PropTypes.bool,
+ showSlider: PropTypes.bool,
step: PropTypes.number,
value: PropTypes.any,
};
diff --git a/src/default_panels/StyleAxesPanel.js b/src/default_panels/StyleAxesPanel.js
index 077255968..620459d67 100644
--- a/src/default_panels/StyleAxesPanel.js
+++ b/src/default_panels/StyleAxesPanel.js
@@ -6,6 +6,7 @@ import {
Dropdown,
FontSelector,
Numeric,
+ NumericFraction,
Radio,
TextEditor,
MenuPanel,
@@ -292,18 +293,8 @@ const StyleAxesPanel = ({localize: _}) => (
(
{label: _('Stretch'), value: 'stretch'},
]}
/>
-
-
+
+
@@ -49,7 +49,7 @@ const StyleImagesPanel = ({localize: _}) => (
-
+
@@ -66,7 +66,7 @@ const StyleImagesPanel = ({localize: _}) => (
-
+
);
diff --git a/src/default_panels/StyleNotesPanel.js b/src/default_panels/StyleNotesPanel.js
index f0dc79b85..a3a92ec88 100644
--- a/src/default_panels/StyleNotesPanel.js
+++ b/src/default_panels/StyleNotesPanel.js
@@ -9,6 +9,7 @@ import {
FontSelector,
Info,
Numeric,
+ PositioningNumeric,
Radio,
TextEditor,
Section,
@@ -40,8 +41,8 @@ const StyleNotesPanel = ({localize: _}) => (
-
-
+
+
@@ -64,7 +65,7 @@ const StyleNotesPanel = ({localize: _}) => (
-
+
@@ -87,7 +88,7 @@ const StyleNotesPanel = ({localize: _}) => (
-
+
);
diff --git a/src/default_panels/StyleShapesPanel.js b/src/default_panels/StyleShapesPanel.js
index b0efbecba..66ddc07cf 100644
--- a/src/default_panels/StyleShapesPanel.js
+++ b/src/default_panels/StyleShapesPanel.js
@@ -5,6 +5,7 @@ import {
Radio,
Section,
PositioningRef,
+ PositioningNumeric,
Numeric,
ColorPicker,
LineDashSelector,
@@ -32,14 +33,14 @@ const StyleShapesPanel = ({localize: _}) => (
diff --git a/src/default_panels/StyleTracesPanel.js b/src/default_panels/StyleTracesPanel.js
index a343f1030..181df8212 100644
--- a/src/default_panels/StyleTracesPanel.js
+++ b/src/default_panels/StyleTracesPanel.js
@@ -10,6 +10,7 @@ import {
LineDashSelector,
LineShapeSelector,
Numeric,
+ NumericFraction,
Radio,
TextEditor,
Section,
@@ -247,54 +248,25 @@ const StyleTracesPanel = ({localize: _}) => (
diff --git a/src/index.js b/src/index.js
index 7dafb090a..cb8703e8d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -40,6 +40,9 @@ import {
Layout,
LayoutNumericFraction,
LayoutNumericFractionInverse,
+ NumericFraction,
+ PositioningNumeric,
+ NumericFractionInverse,
LayoutPanel,
LineDashSelector,
LineShapeSelector,
@@ -98,6 +101,9 @@ export {
Layout,
LayoutNumericFraction,
LayoutNumericFractionInverse,
+ NumericFraction,
+ PositioningNumeric,
+ NumericFractionInverse,
LayoutPanel,
LineDashSelector,
LineShapeSelector,
diff --git a/src/styles/components/fields/_field.scss b/src/styles/components/fields/_field.scss
index 39ddb35ff..297716237 100644
--- a/src/styles/components/fields/_field.scss
+++ b/src/styles/components/fields/_field.scss
@@ -40,6 +40,7 @@
}
&__title {
width: 30%;
+ //flex-shrink:0;
padding-left: var(--spacing-half-unit);
display: block;
font-size: var(--font-size-small);
diff --git a/src/styles/components/widgets/_main.scss b/src/styles/components/widgets/_main.scss
index 9ddc2c0b2..7d8ac46b9 100644
--- a/src/styles/components/widgets/_main.scss
+++ b/src/styles/components/widgets/_main.scss
@@ -6,3 +6,4 @@
@import 'numeric-input';
@import 'radio-block';
@import 'text-editor';
+@import "rangeslider";
diff --git a/src/styles/components/widgets/_numeric-input.scss b/src/styles/components/widgets/_numeric-input.scss
index c0661035c..160229fd5 100644
--- a/src/styles/components/widgets/_numeric-input.scss
+++ b/src/styles/components/widgets/_numeric-input.scss
@@ -1,20 +1,26 @@
-::-webkit-input-placeholder { /* Chrome/Opera/Safari */
+::-webkit-input-placeholder {
+ /* Chrome/Opera/Safari */
color: var(--color-text-placeholder);
}
-::-moz-placeholder { /* Firefox 19+ */
+::-moz-placeholder {
+ /* Firefox 19+ */
color: var(--color-text-placeholder);
}
-:-ms-input-placeholder { /* IE 10+ */
+:-ms-input-placeholder {
+ /* IE 10+ */
color: var(--color-text-placeholder);
}
-:-moz-placeholder { /* Firefox 18- */
+:-moz-placeholder {
+ /* Firefox 18- */
color: var(--color-text-placeholder);
}
-
.numeric-input__wrapper {
line-height: 20px;
max-width: 100%;
+ width: 100%;
+ display: flex;
+ align-items: center;
color: var(--color-text-base);
}
@@ -28,7 +34,8 @@
white-space: nowrap;
text-align: left;
border-radius: var(--border-radius-small);
- padding: var(--spacing-quarter-unit) var(--spacing-quarter-unit) var(--spacing-quarter-unit) var(--spacing-half-unit);
+ padding: var(--spacing-quarter-unit) var(--spacing-quarter-unit)
+ var(--spacing-quarter-unit) var(--spacing-half-unit);
width: 62px;
vertical-align: middle;
font-size: inherit;
diff --git a/src/styles/components/widgets/_rangeslider.scss b/src/styles/components/widgets/_rangeslider.scss
new file mode 100644
index 000000000..4880c950a
--- /dev/null
+++ b/src/styles/components/widgets/_rangeslider.scss
@@ -0,0 +1,205 @@
+/**
+* Rangeslider
+*/
+.rangeslider {
+ margin: 0 var(--spacing-quarter-unit);
+ min-width: 60px;
+ position: relative;
+ background: var(--color-background-light);
+ -ms-touch-action: none;
+ touch-action: none;
+ border: 1px solid var(--color-border-default);
+ flex-grow: 1;
+
+
+ &,
+ .rangeslider__fill {
+ display: block;
+ }
+ .rangeslider__handle {
+ background: #fff;
+ border: 1px solid var(--color-border-default);
+ cursor: pointer;
+ display: inline-block;
+ position: absolute;
+ .rangeslider__active {
+ opacity: 1;
+ }
+ }
+
+ .rangeslider__handle-tooltip {
+ $size: 20px;
+ width: $size;
+ height: $size;
+ text-align: center;
+ position: absolute;
+ background-color: rgba(0, 0, 0, 0.8);
+ font-weight: normal;
+ font-size: 14px;
+ transition: all 100ms ease-in;
+ border-radius: 4px;
+ display: inline-block;
+ color: white;
+ left: 50%;
+ transform: translate3d(-50%, 0, 0);
+ span {
+ margin-top: 12px;
+ display: inline-block;
+ line-height: 100%;
+ }
+ &:after {
+ content: ' ';
+ position: absolute;
+ width: 0;
+ height: 0;
+ }
+ }
+}
+
+/**
+* Rangeslider - Horizontal slider
+*/
+.rangeslider-horizontal {
+ height: 6px;
+ border-radius: 10px;
+ .rangeslider__fill {
+ height: 100%;
+ background-color: var(--color-accent);
+ border: var(--border-accent);
+ border-radius: 10px;
+ transform: translateY(-1px);
+ top: 0;
+ }
+ .rangeslider__handle {
+ $size: 20px;
+ width: $size/3;
+ height: $size*1.5;
+ border-radius: $size;
+ top: 50%;
+ transform: translate3d(-50%, -50%, 0);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ &:after {
+ content: ' ';
+ position: absolute;
+ width: $size/5;
+ height: $size;
+ border-radius: $size/4;
+ background-color: var(--color-accent);
+ display: none;
+ }
+ }
+ .rangeslider__handle-tooltip {
+ top: -55px;
+ &:after {
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-top: 8px solid rgba(0, 0, 0, 0.8);
+ left: 50%;
+ bottom: -8px;
+ transform: translate3d(-50%, 0, 0);
+ }
+ }
+}
+
+/**
+* Rangeslider - Vertical slider
+*/
+.rangeslider-vertical {
+ margin: 20px auto;
+ height: 150px;
+ max-width: 10px;
+ background-color: transparent;
+
+ .rangeslider__fill,
+ .rangeslider__handle {
+ position: absolute;
+ }
+
+ .rangeslider__fill {
+ width: 100%;
+ background-color: #7cb342;
+ box-shadow: none;
+ bottom: 0;
+ }
+ .rangeslider__handle {
+ width: 30px;
+ height: 10px;
+ left: -10px;
+ box-shadow: none;
+ }
+ .rangeslider__handle-tooltip {
+ left: -100%;
+ top: 50%;
+ transform: translate3d(-50%, -50%, 0);
+ &:after {
+ border-top: 8px solid transparent;
+ border-bottom: 8px solid transparent;
+ border-left: 8px solid rgba(0, 0, 0, 0.8);
+ left: 100%;
+ top: 12px;
+ }
+ }
+}
+
+/**
+* Rangeslider - Reverse
+*/
+
+.rangeslider-reverse {
+ &.rangeslider-horizontal {
+ .rangeslider__fill {
+ right: 0;
+ }
+ }
+ &.rangeslider-vertical {
+ .rangeslider__fill {
+ top: 0;
+ bottom: inherit;
+ }
+ }
+}
+
+/**
+* Rangeslider - Labels
+*/
+.rangeslider__labels {
+ position: relative;
+ .rangeslider-vertical & {
+ position: relative;
+ list-style-type: none;
+ margin: 0 0 0 24px;
+ padding: 0;
+ text-align: left;
+ width: 250px;
+ height: 100%;
+ left: 10px;
+
+ .rangeslider__label-item {
+ position: absolute;
+ transform: translate3d(0, -50%, 0);
+
+ &::before {
+ content: '';
+ width: 10px;
+ height: 2px;
+ background: black;
+ position: absolute;
+ left: -14px;
+ top: 50%;
+ transform: translateY(-50%);
+ z-index: -1;
+ }
+ }
+ }
+
+ .rangeslider__label-item {
+ position: absolute;
+ font-size: 14px;
+ cursor: pointer;
+ display: inline-block;
+ top: 10px;
+ transform: translate3d(-50%, 0, 0);
+ }
+}