-
-
- {this.props.children}
+ {label ? (
+
+ ) : null}
+
+ {children}
+ {multiValued ? (
+
+ {_(multiValueText.title)}
+ {_(multiValueText.text)}
+ {_(multiValueText.subText)}
+
+ ) : null}
- {postfix}
+ {postfix ? (
+
+ ) : null}
);
}
@@ -45,9 +68,15 @@ export default class Field extends Component {
Field.propTypes = {
center: PropTypes.bool,
label: PropTypes.string,
+ localize: PropTypes.func,
postfix: PropTypes.string,
+ multiValued: PropTypes.bool,
+ children: PropTypes.node,
};
Field.defaultProps = {
center: false,
+ multiValued: false,
};
+
+export default localize(Field);
diff --git a/src/components/fields/Info.js b/src/components/fields/Info.js
index 0a431c1bb..a3cd6734f 100644
--- a/src/components/fields/Info.js
+++ b/src/components/fields/Info.js
@@ -1,5 +1,4 @@
import Field from './Field';
-import PropTypes from 'prop-types';
import React, {Component} from 'react';
export default class Info extends Component {
diff --git a/src/components/fields/Numeric.js b/src/components/fields/Numeric.js
index fa1222368..7a6a20496 100644
--- a/src/components/fields/Numeric.js
+++ b/src/components/fields/Numeric.js
@@ -2,7 +2,7 @@ import Field from './Field';
import NumericInput from '../widgets/NumericInputStatefulWrapper';
import PropTypes from 'prop-types';
import React, {Component} from 'react';
-import {bem, connectToContainer} from '../../lib';
+import {connectToContainer} from '../../lib';
class Numeric extends Component {
render() {
@@ -10,12 +10,13 @@ class Numeric extends Component {
);
@@ -23,12 +24,18 @@ class Numeric extends Component {
}
Numeric.propTypes = {
+ defaultValue: PropTypes.number,
fullValue: PropTypes.func,
min: PropTypes.number,
max: PropTypes.number,
+ showArrows: PropTypes.bool,
step: PropTypes.number,
updatePlot: PropTypes.func,
...Field.propTypes,
};
+Numeric.defaultProps = {
+ showArrows: true,
+};
+
export default connectToContainer(Numeric);
diff --git a/src/components/fields/Radio.js b/src/components/fields/Radio.js
index d803905c2..e655363ce 100644
--- a/src/components/fields/Radio.js
+++ b/src/components/fields/Radio.js
@@ -2,7 +2,7 @@ import PropTypes from 'prop-types';
import React, {Component} from 'react';
import RadioBlocks from '../widgets/RadioBlocks';
import Field from './Field';
-import {bem, connectToContainer} from '../../lib';
+import {connectToContainer} from '../../lib';
class Radio extends Component {
render() {
diff --git a/src/components/fields/SymbolSelector.js b/src/components/fields/SymbolSelector.js
new file mode 100644
index 000000000..2a58b1bb7
--- /dev/null
+++ b/src/components/fields/SymbolSelector.js
@@ -0,0 +1,58 @@
+import Field from './Field';
+import PropTypes from 'prop-types';
+import React, {Component} from 'react';
+import SymbolSelectorWidget from '../widgets/SymbolSelector';
+import nestedProperty from 'plotly.js/src/lib/nested_property';
+import {SYMBOLS} from '../../lib/constants';
+import {connectToContainer} from '../../lib';
+
+class SymbolSelector extends Component {
+ render() {
+ const {fullContainer, fullValue, updatePlot} = this.props;
+
+ const markerColor = nestedProperty(fullContainer, 'marker.color').get();
+ const borderWidth = nestedProperty(
+ fullContainer,
+ 'marker.line.width'
+ ).get();
+
+ let borderColor = markerColor;
+ if (borderWidth) {
+ borderColor = nestedProperty(fullContainer, 'marker.line.color').get();
+ }
+
+ let symbolOptions;
+ if (this.props.is3D) {
+ symbolOptions = SYMBOLS.filter(option => {
+ return option.threeD;
+ });
+ } else {
+ symbolOptions = [...SYMBOLS];
+ }
+
+ return (
+
+
+
+ );
+ }
+}
+
+SymbolSelector.propTypes = {
+ defaultValue: PropTypes.number,
+ fullValue: PropTypes.func,
+ updatePlot: PropTypes.func,
+ ...Field.propTypes,
+};
+
+SymbolSelector.defaultProps = {
+ showArrows: true,
+};
+
+export default connectToContainer(SymbolSelector);
diff --git a/src/components/fields/TraceSelector.js b/src/components/fields/TraceSelector.js
index 2032d6901..95dec45b7 100644
--- a/src/components/fields/TraceSelector.js
+++ b/src/components/fields/TraceSelector.js
@@ -4,14 +4,68 @@ import React, {Component} from 'react';
import nestedProperty from 'plotly.js/src/lib/nested_property';
import {connectToContainer} from '../../lib';
+function computeTraceOptionsFromSchema(schema) {
+ const capitalize = s => s.charAt(0).toUpperCase() + s.substring(1);
+
+ // Filter out Polar "area" type as it is fairly broken and we want to present
+ // scatter with fill as an "area" chart type for convenience.
+ const traceTypes = Object.keys(schema.traces).filter(t => t !== 'area');
+
+ const labels = traceTypes.map(capitalize);
+ const traceOptions = traceTypes.map((t, i) => ({
+ label: labels[i],
+ value: t,
+ }));
+
+ const i = traceOptions.findIndex(opt => opt.value === 'scatter');
+ traceOptions.splice(
+ i + 1,
+ 0,
+ {label: 'Line', value: 'line'},
+ {label: 'Area', value: 'area'}
+ );
+
+ return traceOptions;
+}
+
class TraceSelector extends Component {
constructor(props, context) {
super(props, context);
this.updatePlot = this.updatePlot.bind(this);
this.fullValue = this.fullValue.bind(this);
- const scatterAttrs = this.context.plotSchema.traces.scatter.attributes;
- this.fillTypes = scatterAttrs.fill.values.filter(v => v !== 'none');
+ let fillMeta;
+ if (props.getValObject) {
+ fillMeta = props.getValObject('fill');
+ }
+ if (fillMeta) {
+ this.fillTypes = fillMeta.values.filter(v => v !== 'none');
+ } else {
+ this.fillTypes = [
+ 'tozeroy',
+ 'tozerox',
+ 'tonexty',
+ 'tonextx',
+ 'toself',
+ 'tonext',
+ ];
+ }
+
+ this.setLocals(props, context);
+ }
+
+ setLocals(props, context) {
+ if (props.traceOptions) {
+ this.traceOptions = props.traceOptions;
+ } else if (context.plotSchema) {
+ this.traceOptions = computeTraceOptionsFromSchema(context.plotSchema);
+ } else {
+ this.traceOptions = [{label: 'Scatter', value: 'scatter'}];
+ }
+ }
+
+ componentWillReceiveProps(nextProps, nextContext) {
+ this.setLocals(nextProps, nextContext);
}
updatePlot(value) {
@@ -26,18 +80,19 @@ class TraceSelector extends Component {
update = {type: value};
}
- this.props.updateContainer && this.props.updateContainer(update);
+ if (this.props.updateContainer) {
+ this.props.updateContainer(update);
+ }
}
fullValue() {
- const type = this.props.fullValue();
+ const {container, fullValue} = this.props;
+ const type = fullValue();
- // we use gd.data instead of fullData so that we can show the trace
- // even if the trace is not visible due to missing data.
- // If we used fullData mode or fill will be undefined as the fullTrace
- // isn't computed when not visible.
- const mode = nestedProperty(this.props.trace, 'mode').get();
- const fill = nestedProperty(this.props.trace, 'fill').get();
+ // If we used fullData mode or fill it may be undefined if the fullTrace
+ // is not visible and therefore does not have these values computed.
+ const mode = nestedProperty(container, 'mode').get();
+ const fill = nestedProperty(container, 'fill').get();
if (type === 'scatter' && this.fillTypes.includes(fill)) {
return 'area';
@@ -54,6 +109,7 @@ class TraceSelector extends Component {
const props = Object.assign({}, this.props, {
fullValue: this.fullValue,
updatePlot: this.updatePlot,
+ options: this.traceOptions,
});
return
;
@@ -64,4 +120,11 @@ TraceSelector.contextTypes = {
plotSchema: PropTypes.object,
};
+TraceSelector.propTypes = {
+ getValObject: PropTypes.func,
+ container: PropTypes.object.isRequired,
+ fullValue: PropTypes.func.isRequired,
+ updateContainer: PropTypes.func,
+};
+
export default connectToContainer(TraceSelector);
diff --git a/src/components/fields/index.js b/src/components/fields/index.js
index a18b3f12f..2d3d9e199 100644
--- a/src/components/fields/index.js
+++ b/src/components/fields/index.js
@@ -1,3 +1,6 @@
+import AxesRange from './AxesRange';
+import AxesSelector from './AxesSelector';
+import CanvasSize from './CanvasSize';
import ColorPicker from './Color';
import Dropdown from './Dropdown';
import Flaglist from './Flaglist';
@@ -5,9 +8,13 @@ import Info from './Info';
import Radio from './Radio';
import DataSelector from './DataSelector';
import Numeric from './Numeric';
+import SymbolSelector from './SymbolSelector';
import TraceSelector from './TraceSelector';
export {
+ AxesRange,
+ AxesSelector,
+ CanvasSize,
ColorPicker,
Dropdown,
Flaglist,
@@ -15,5 +22,6 @@ export {
Radio,
DataSelector,
Numeric,
+ SymbolSelector,
TraceSelector,
};
diff --git a/src/components/index.js b/src/components/index.js
index 4bd7f548c..42ac7373e 100644
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,4 +1,7 @@
import {
+ AxesRange,
+ AxesSelector,
+ CanvasSize,
ColorPicker,
Dropdown,
Flaglist,
@@ -6,14 +9,25 @@ import {
Radio,
DataSelector,
Numeric,
+ SymbolSelector,
TraceSelector,
} from './fields';
-import {SubPanel, Fold, Panel, Section, TraceAccordion} from './containers';
+import {
+ MenuPanel,
+ Fold,
+ Panel,
+ Section,
+ TraceAccordion,
+ TraceMarkerSection,
+} from './containers';
import PanelMenuWrapper from './PanelMenuWrapper';
export {
- SubPanel,
+ AxesSelector,
+ AxesRange,
+ MenuPanel,
+ CanvasSize,
ColorPicker,
DataSelector,
Dropdown,
@@ -25,6 +39,8 @@ export {
PanelMenuWrapper,
Radio,
Section,
+ SymbolSelector,
TraceAccordion,
+ TraceMarkerSection,
TraceSelector,
};
diff --git a/src/components/widgets/EditableText.js b/src/components/widgets/EditableText.js
index 7dd41616c..43f5ef9a8 100644
--- a/src/components/widgets/EditableText.js
+++ b/src/components/widgets/EditableText.js
@@ -89,8 +89,8 @@ EditableText.propTypes = {
// Called on input keyDown events
onKeyDown: PropTypes.func,
- // Input value property
- text: PropTypes.string,
+ // Input value property ...
+ text: PropTypes.any,
// Input properties
placeholder: PropTypes.string,
diff --git a/src/components/widgets/NumericInput.js b/src/components/widgets/NumericInput.js
deleted file mode 100644
index 7b7416413..000000000
--- a/src/components/widgets/NumericInput.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import EditableText from "./EditableText";
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import isNumeric from "fast-isnumeric";
-import classnames from "classnames";
-
-export const UP_ARROW = 38;
-export const DOWN_ARROW = 40;
-export const TEST_SELECTOR_CLASS = "js-NumericInput";
-
-export default class NumericInput extends Component {
- constructor(props) {
- super(props);
-
- this.onKeyDown = this.onKeyDown.bind(this);
- this.incrementValue = this.incrementValue.bind(this);
- }
-
- onKeyDown(e) {
- switch (e.keyCode) {
- case UP_ARROW:
- return this.incrementValue("increase");
- case DOWN_ARROW:
- return this.incrementValue("decrease");
- default:
- break;
- }
- }
-
- incrementValue(direction) {
- const step = this.props.step || 1;
- let currentValue = this.props.value;
-
- if (isNumeric(this.props.value)) {
- if (direction === "increase") {
- currentValue = currentValue + step;
- } else {
- currentValue = currentValue - step;
- }
- }
-
- // incrementers blur the line between blur and onChange.
- if (this.props.onUpdate) {
- this.props.onUpdate(currentValue);
- } else {
- this.props.onChange(currentValue);
- }
- }
-
- renderArrows() {
- if (!this.props.showArrows) {
- return;
- }
-
- return (
-
- );
- }
-
- render() {
- const wrapperClassName = classnames("numeric-input__wrapper");
-
- const editableClass = classnames(
- "numeric-input__number",
- this.props.editableClassName,
- TEST_SELECTOR_CLASS
- );
-
- return (
-
-
- {this.renderArrows()}
-
- );
- }
-}
-
-/*NumericInput.propTypes = {
- value: customPropTypes.customOneOfType([
- PropTypes.string,
- customPropTypes.isNumeric,
- customPropTypes.isNull,
- ]).isDefined,
- onChange: PropTypes.func.isRequired,
- onUpdate: PropTypes.func,
- step: PropTypes.number,
- showArrows: PropTypes.bool,
- editableClassName: PropTypes.string,
-};*/
-
-NumericInput.defaultProps = {
- showError: false,
- showArrows: true,
-};
diff --git a/src/components/widgets/NumericInputStatefulWrapper.js b/src/components/widgets/NumericInputStatefulWrapper.js
index ea6a49aae..7d7f12e0d 100644
--- a/src/components/widgets/NumericInputStatefulWrapper.js
+++ b/src/components/widgets/NumericInputStatefulWrapper.js
@@ -1,191 +1,146 @@
-import NumericInput from "./NumericInput";
-import React, { Component } from "react";
-import PropTypes from "prop-types";
-import isNumeric from "fast-isnumeric";
-import { MIXED_VALUES, MIXED_MODE_VALUE } from "../../lib/constants";
-import { clamp } from "../../lib";
-
-// mapPropsToState, What is this absurdity?!? NumericInputStatefulWrapper
-// maintains state so that users can muck around in the inner NumericInput
-// input box. We don't want to fire updates() each time a user enters a
-// character. Only when the user blurs do we want the update method to be fired.
-// So why map props onto state? The internal state is mapped to the inputbox
-// and with MIXED_VALUE mode we need a way to forcibly change the characters in
-// the inputbox. So incoming props update state but the user is then permitted
-// to make textual changes to the inputbox outside of the knowledge of the
-// Store. Then onBlur we fire onUpdate and the Store can decide whether to keep
-// the value the user inputed or change it to something else. There is also
-// an edge case where we are in mixedMode and showing some special character in
-// the inputbox "-" and the user tries to manually edit the input box with
-// garbage and move on. To make it clear that we are still in mixedMode and that
-// no other inputs have been changed we revert their garbage back to "-".
-// This requires a setState inside the onUpdate method.
-function mapPropsToState({ value: propValue, defaultValue = 0 }) {
- let value;
- const mixedMode = propValue === MIXED_VALUES;
-
- if (mixedMode) {
- // MixedMode is useful when indicating to the user that there
- // is another source of value coming from somewhere else in the
- // app which renders this control optional. For example a user
- // may have selected a value for xaxis range and is now exploring
- // the UI for applying ranges to "all axes". In this case a
- // mixedValue is shown so the user has some visual information that
- // applying a value to "all axes" will somehow supercede some related
- // value elsewhere. WS2 also provides a more helpful message in these
- // cases than just the MIXED_MODE_VALUE
- value = MIXED_MODE_VALUE;
- } else if (propValue === null) {
- // Null is used throughout the App to represent "no value."
- // This may be an unfortunate decision but NumericInput supports
- // null by showing the user that the value is actually
- // "defaultValue" or 0.
- // Actually it would be nice to take this chunk of code out.
- value = defaultValue;
- } else {
- value = propValue;
- }
+import EditableText from './EditableText';
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import isNumeric from 'fast-isnumeric';
- return { value, mixedMode };
-}
+export const UP_ARROW = 38;
+export const DOWN_ARROW = 40;
export default class NumericInputStatefulWrapper extends Component {
constructor(props) {
super(props);
- this.state = mapPropsToState(props);
-
+ this.state = {value: props.value};
this.onChange = this.onChange.bind(this);
- this.onUpdate = this.onUpdate.bind(this);
+ this.updateValue = this.updateValue.bind(this);
}
componentWillReceiveProps(nextProps) {
if (nextProps.value !== this.state.value) {
- this.setState(mapPropsToState(nextProps));
+ this.setState({value: nextProps.value});
+ }
+ }
+
+ onKeyDown(e) {
+ switch (e.keyCode) {
+ case UP_ARROW:
+ this.incrementValue('increase');
+ break;
+ case DOWN_ARROW:
+ this.incrementValue('decrease');
+ break;
+ default:
+ break;
}
}
onChange(value) {
- /*
- * Mixed Mode is preserved until new props are sent down from
- * upstream components
- */
- this.setState({ value });
+ this.setState({value});
}
- onUpdate(value) {
- const { defaultValue, integerOnly, max, min } = this.props;
-
- // defaultValue is truthy or numeric (account for falsey 0)
- const hasDefaultValue = defaultValue || isNumeric(defaultValue);
- let updatedValue = value;
-
- // If we are in mixed mode and receive the placeholder value then
- // the user is attempting to increment or decrement. If we are in
- // mixed mode and receive some other value then the user has entered
- // this value explicitly in the inputbox and is bluring away.
- // In the case of incrementing and decrementing we set the updatedValue
- // to the default value or min or 0. If the value is set explicitly and
- // is numeric we do the same --- call onUpdate. This allows upstream
- // components to send in new props that toggle this component out of
- // mixedValue state. If it is set explicitly in the input box but is not
- // numeric onUpdate is not called and mixedMode is maintained.
- // In this case we also force MIXED_MODE_VALUE so the user is aware that
- // no settings have actually been changed.
- if (this.state.mixedMode && updatedValue === MIXED_MODE_VALUE) {
- const fallbackValue = min || 0;
- updatedValue = hasDefaultValue ? defaultValue : fallbackValue;
- } else if (this.state.mixedMode && !isNumeric(updatedValue)) {
- // mixed mode takes precedence over showing default values when
- // empty strings are input. We return early to bypass that logic.
- this.setState({ value: MIXED_MODE_VALUE });
+ updateValue(newValue) {
+ const {max, min, integerOnly, value: propsValue} = this.props;
+ let updatedValue = newValue;
+
+ // When the user blurs on non-numeric data reset the component
+ // to the last known good value (this.props.value).
+ if (!isNumeric(updatedValue)) {
+ this.setState({value: propsValue});
return;
}
- // If supplied a default value use it when the user blurs on an
- // empty string or string made up of spaces.
- if (typeof updatedValue === "string" && hasDefaultValue) {
- updatedValue = updatedValue.replace(/^\s+/g, "");
- if (updatedValue.length === 0) {
- updatedValue = defaultValue;
- }
+ updatedValue = Number(updatedValue);
+ if (integerOnly) {
+ updatedValue = Math.floor(updatedValue);
}
- // When correct input is supplied by the user constrain it to be within
- // [max, min] if max and min are supplied. Ditto for forcing an
- // integer value. We take the floor instead of rounding
- // as that is/(may be) less confusing to the user visually.
- const numericBounds = isNumeric(min) && isNumeric(max);
- if (isNumeric(updatedValue)) {
- updatedValue = Number(updatedValue);
+ if (isNumeric(min)) {
+ updatedValue = Math.max(min, updatedValue);
+ }
- if (integerOnly) {
- updatedValue = Math.floor(updatedValue);
- }
+ if (isNumeric(max)) {
+ updatedValue = Math.min(max, updatedValue);
+ }
+
+ this.props.onUpdate(updatedValue);
+ }
+
+ incrementValue(direction) {
+ const {defaultValue, min, step = 1} = this.props;
+ const {value} = this.state;
- if (numericBounds) {
- updatedValue = clamp(updatedValue, min, max);
- } else if (isNumeric(min)) {
- updatedValue = Math.max(min, updatedValue);
- } else if (isNumeric(max)) {
- updatedValue = Math.min(max, updatedValue);
+ let valueUpdate;
+ if (isNumeric(value)) {
+ if (direction === 'increase') {
+ valueUpdate = value + step;
+ } else {
+ valueUpdate = value - step;
}
+ } else {
+ // if we are multi-valued and the user is incrementing or decrementing
+ // update with some sane value so we can "break" out of multi-valued mode.
+ if (isNumeric(defaultValue)) {
+ valueUpdate = defaultValue;
+ } else {
+ // TODO smarter handling depending if user decrements or increments?
+ valueUpdate = min || 0;
+ }
+ }
+
+ // incrementers blur the line between blur and onChange.
+ this.updateValue(valueUpdate);
+ }
- this.props.onUpdate(updatedValue);
+ renderArrows() {
+ if (!this.props.showArrows) {
+ return null;
}
+
+ return (
+
+ );
}
render() {
return (
-
+
+
+ {this.renderArrows()}
+
);
}
}
NumericInputStatefulWrapper.propTypes = {
- // defaultValue is default value used when
- // A) a user leaves the input empty or filled with spaces.
- // B) a user is moving out of mixed mode.
- // C) a `null` value is supplied to this component.
defaultValue: PropTypes.number,
editableClassName: PropTypes.string,
-
- // When integerOnly flag is set any numeric input supplied by
- // the user is constrained to be a whole integer number.
- // Math.floor is used for this operation.
integerOnly: PropTypes.bool,
-
- // If min is supplied and defaultValue is *not* supplied the min
- // value will be used when the user moves out of mixed mode.
- // If both min and max are supplied they are used to constrain
- // numeric input from the user to be within this range.
max: PropTypes.number,
min: PropTypes.number,
-
- // Handler run onBlur and called with the updated value.
onUpdate: PropTypes.func.isRequired,
-
- // showArrows is a flag that will show or hide the increment and
- // decrement buttons on the side of the inputbox. Defaults to true.
showArrows: PropTypes.bool,
-
- // If incrementors are present step size controls the numeric step taken
- // when incrementing and decrementing.
step: PropTypes.number,
-
value: PropTypes.any,
- /*value: customPropTypes.customOneOfType([
- PropTypes.string,
- customPropTypes.isNumeric,
- customPropTypes.isNull,
- ]).isDefined,*/
};
NumericInputStatefulWrapper.defaultProps = {
diff --git a/src/components/widgets/SymbolSelector.js b/src/components/widgets/SymbolSelector.js
new file mode 100644
index 000000000..f96fcdbd5
--- /dev/null
+++ b/src/components/widgets/SymbolSelector.js
@@ -0,0 +1,143 @@
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import tinyColor from 'tinycolor2';
+import ModalBox from '../containers/ModalBox';
+
+const tooLightFactor = 0.8;
+
+function tooLight(color) {
+ const hslColor = tinyColor(color).toHsl();
+ return hslColor.l > tooLightFactor;
+}
+
+export default class SymbolSelector extends Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ isOpen: false,
+ };
+ this.togglePanel = this.togglePanel.bind(this);
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ const {markerColor, borderColor} = this.props;
+ const {
+ markerColor: nextMarkerColor,
+ borderColor: nextBorderColor,
+ } = nextProps;
+
+ return (
+ this.props.value !== nextProps.value ||
+ this.state.isOpen !== nextState.isOpen ||
+ markerColor !== nextMarkerColor ||
+ borderColor !== nextBorderColor
+ );
+ }
+
+ togglePanel() {
+ this.setState({isOpen: !this.state.isOpen});
+ }
+
+ renderActiveOption() {
+ const {markerColor, borderColor, symbolOptions, value} = this.props;
+ const currentSymbol = symbolOptions.find(symbol => symbol.value === value);
+ if (!currentSymbol) {
+ return (
+
+ {'-'}
+
+ );
+ }
+
+ const symbolStyle = {
+ stroke: currentSymbol.fill === 'none' ? markerColor : borderColor,
+ strokeOpacity: '1',
+ strokeWidth: '2px',
+ fill: currentSymbol.fill === 'none' ? 'none' : markerColor,
+ };
+
+ return (
+
+
+
+
+
+
+
+ );
+ }
+
+ renderOptions() {
+ const {markerColor, borderColor, symbolOptions} = this.props;
+ return symbolOptions.map(option => {
+ const {fill, value, label} = option;
+
+ const symbolStyle = {
+ stroke: fill === 'none' ? markerColor : borderColor,
+ strokeOpacity: '1',
+ strokeWidth: '2px',
+ fill: fill === 'none' ? 'none' : markerColor,
+ };
+ return (
+
+ );
+ });
+ }
+
+ render() {
+ const {isOpen} = this.state;
+ const {markerColor} = this.props;
+
+ // TODO link these colors into theme
+ const backgroundColor = tooLight(markerColor) ? '#bec8d9' : 'white';
+
+ return (
+
+
+
+ {this.renderActiveOption()}
+
+
+
+
+
+ {isOpen ? (
+
+ {this.renderOptions()}
+
+ ) : null}
+
+ );
+ }
+}
+
+SymbolSelector.propTypes = {
+ markerColor: PropTypes.string,
+ borderColor: PropTypes.string,
+ value: PropTypes.string,
+ onChange: PropTypes.func,
+ symbolOptions: PropTypes.array,
+};
diff --git a/src/index.js b/src/index.js
index 803873da8..15c16f022 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,6 +2,7 @@ import Hub from './hub';
import PlotlyEditor from './PlotlyEditor';
import {
localize,
+ connectAxesToLayout,
connectLayoutToPlot,
connectToContainer,
connectTraceToPlot,
@@ -9,7 +10,9 @@ import {
import {EDITOR_ACTIONS} from './constants';
import {
- SubPanel,
+ AxesRange,
+ AxesSelector,
+ CanvasSize,
ColorPicker,
DataSelector,
Dropdown,
@@ -22,12 +25,18 @@ import {
PanelMenuWrapper,
Radio,
Section,
+ MenuPanel,
+ SymbolSelector,
TraceAccordion,
+ TraceMarkerSection,
TraceSelector,
} from './components';
export {
- SubPanel,
+ AxesRange,
+ AxesSelector,
+ MenuPanel,
+ CanvasSize,
ColorPicker,
DataSelector,
Dropdown,
@@ -42,8 +51,11 @@ export {
PanelMenuWrapper,
Radio,
Section,
+ SymbolSelector,
TraceAccordion,
+ TraceMarkerSection,
TraceSelector,
+ connectAxesToLayout,
connectLayoutToPlot,
connectToContainer,
connectTraceToPlot,
diff --git a/src/lib/connectAxesToLayout.js b/src/lib/connectAxesToLayout.js
new file mode 100644
index 000000000..2af048572
--- /dev/null
+++ b/src/lib/connectAxesToLayout.js
@@ -0,0 +1,238 @@
+import React, {Component} from 'react';
+import PropTypes from 'prop-types';
+import nestedProperty from 'plotly.js/src/lib/nested_property';
+import {MULTI_VALUED} from './constants';
+import {getDisplayName, isPlainObject} from '../lib';
+
+/**
+ * Simple replacer to use with JSON.stringify.
+ * @param {*} key Current object key.
+ * @param {*} value Current value in object at key.
+ * @returns {*} If we return undefined, the key is skipped in JSON.stringify.
+ */
+function skipPrivateKeys(key, value) {
+ if (key.startsWith('_')) {
+ return void 0;
+ }
+
+ return value;
+}
+
+/**
+ * Deep-copies the value using JSON. Underscored (private) keys are removed.
+ * @param {*} value Some nested value from the plotDiv object.
+ * @returns {*} A deepcopy of the value.
+ */
+function deepCopyPublic(value) {
+ if (typeof value === 'undefined') {
+ return value;
+ }
+
+ return window.JSON.parse(window.JSON.stringify(value, skipPrivateKeys));
+}
+
+/*
+ * Test that we can connectLayoutToPlot(connectAxesToLayout(Panel))
+ */
+function setMultiValuedContainer(intoObj, fromObj, key, config = {}) {
+ var intoVal = intoObj[key],
+ fromVal = fromObj[key];
+
+ var searchArrays = config.searchArrays;
+
+ // don't merge private attrs
+ if (
+ (typeof key === 'string' && key.charAt(0) === '_') ||
+ typeof intoVal === 'function' ||
+ key === 'module'
+ ) {
+ return;
+ }
+
+ // already a mixture of values, can't get any worse
+ if (intoVal === MULTI_VALUED) {
+ return;
+ } else if (intoVal === void 0) {
+ // if the original doesn't have the key it's because that key
+ // doesn't do anything there - so use the new value
+ // note that if fromObj doesn't have a key in intoObj we will not
+ // attempt to merge them at all, so this behavior makes the merge
+ // independent of order.
+ intoObj[key] = fromVal;
+ } else if (key === 'colorscale') {
+ // colorscales are arrays... need to stringify before comparing
+ // (other vals we don't want to stringify, as differences could
+ // potentially be real, like 'false' and false)
+ if (String(intoVal) !== String(fromVal)) {
+ intoObj[key] = MULTI_VALUED;
+ }
+ } else if (Array.isArray(intoVal)) {
+ // in data, other arrays are data, which we don't care about
+ // for styling purposes
+ if (!searchArrays) {
+ return;
+ }
+ // in layout though, we need to recurse into arrays
+ for (var i = 0; i < fromVal.length; i++) {
+ setMultiValuedContainer(intoVal, fromVal, i, searchArrays);
+ }
+ } else if (isPlainObject(fromVal)) {
+ // recurse into objects
+ if (!isPlainObject(intoVal)) {
+ throw new Error('tried to merge object into non-object: ' + key);
+ }
+ Object.keys(fromVal).forEach(function(key2) {
+ setMultiValuedContainer(intoVal, fromVal, key2, searchArrays);
+ });
+ } else if (isPlainObject(intoVal)) {
+ throw new Error('tried to merge non-object into object: ' + key);
+ } else if (intoVal !== fromVal) {
+ // different non-empty values -
+ intoObj[key] = MULTI_VALUED;
+ }
+}
+
+function computeAxesOptions(fullContainer, axes) {
+ const options = [{label: 'All Axes', value: 'allaxes'}];
+ for (let i = 0; i < axes.length; i++) {
+ const ax = axes[i];
+ const axesPrefix = ax._id.length > 1 ? ' ' + ax._id.substr(1) : '';
+ const label = `${ax._id.charAt(0).toUpperCase()} Axis${axesPrefix}`;
+ options[i + 1] = {label, value: ax._name};
+ }
+
+ return options;
+}
+
+export default function connectAxesToLayout(WrappedComponent) {
+ class AxesConnectedComponent extends Component {
+ constructor(props, context) {
+ super(props, context);
+
+ this.state = {axesTarget: this.props.defaultAxesTarget};
+ this.axesTargetHandler = this.axesTargetHandler.bind(this);
+ this.updateContainer = this.updateContainer.bind(this);
+
+ this.setLocals(props, this.state, context);
+ }
+
+ componentWillUpdate(nextProps, nextState, nextContext) {
+ // This is not enough, what if plotly.js adds new axes...
+ this.setLocals(nextProps, nextState, nextContext);
+ }
+
+ // This function should be optimized. We can compare a list of
+ // axesNames to nextAxesNames and check gd.layout[axesN] for shallow
+ // equality. Unfortunately we are currently mutating gd.layout so the
+ // shallow check is not possible.
+ setLocals(nextProps, nextState, nextContext) {
+ const {plotly, graphDiv, container, fullContainer} = nextContext;
+ const {axesTarget} = nextState;
+ if (plotly) {
+ this.axes = plotly.Axes.list(graphDiv);
+ } else {
+ this.axes = [];
+ }
+ this.axesOptions = computeAxesOptions(fullContainer, this.axes);
+
+ if (axesTarget === 'allaxes') {
+ const multiValuedContainer = deepCopyPublic(this.axes[0]);
+ this.axes.slice(1).forEach(ax =>
+ Object.keys(ax).forEach(key =>
+ setMultiValuedContainer(multiValuedContainer, ax, key, {
+ searchArrays: true,
+ })
+ )
+ );
+ this.fullContainer = multiValuedContainer;
+ this.defaultContainer = this.axes[0];
+ // what should this be set to? Probably doesn't matter.
+ this.container = {};
+ } else {
+ this.fullContainer = nestedProperty(fullContainer, axesTarget).get();
+ this.container = nestedProperty(container, axesTarget).get();
+ }
+ }
+
+ getChildContext() {
+ return {
+ axesOptions: this.axesOptions,
+ axesTarget: this.state.axesTarget,
+ axesTargetHandler: this.axesTargetHandler,
+ container: this.container,
+ defaultContainer: this.defaultContainer,
+ fullContainer: this.fullContainer,
+ updateContainer: this.updateContainer,
+ };
+ }
+
+ axesTargetHandler(axesTarget) {
+ this.setState({axesTarget});
+ }
+
+ updateContainer(update) {
+ const newUpdate = {};
+ const {axesTarget} = this.state;
+
+ let axes = this.axes;
+ if (axesTarget !== 'allaxes') {
+ // only the selected container
+ axes = [this.fullContainer];
+ }
+
+ const keys = Object.keys(update);
+ for (let i = 0; i < keys.length; i++) {
+ for (let j = 0; j < axes.length; j++) {
+ const scene = axes[j]._id.substr(1);
+ let axesKey = axes[j]._name;
+
+ // scenes are nested
+ if (scene.indexOf('scene') !== -1) {
+ axesKey = `${scene}.${axesKey}`;
+ }
+
+ const newkey = `${axesKey}.${keys[i]}`;
+ newUpdate[newkey] = update[keys[i]];
+ }
+ }
+
+ this.context.updateContainer(newUpdate);
+ }
+
+ render() {
+ return
;
+ }
+ }
+
+ AxesConnectedComponent.displayName = `AxesConnected${getDisplayName(
+ WrappedComponent
+ )}`;
+
+ AxesConnectedComponent.propTypes = {
+ defaultAxesTarget: PropTypes.string,
+ };
+
+ AxesConnectedComponent.defaultProps = {
+ defaultAxesTarget: 'xaxis',
+ };
+
+ AxesConnectedComponent.contextTypes = {
+ container: PropTypes.object.isRequired,
+ fullContainer: PropTypes.object.isRequired,
+ graphDiv: PropTypes.object.isRequired,
+ plotly: PropTypes.object,
+ updateContainer: PropTypes.func,
+ };
+
+ AxesConnectedComponent.childContextTypes = {
+ axesOptions: PropTypes.array,
+ axesTarget: PropTypes.string,
+ axesTargetHandler: PropTypes.func,
+ container: PropTypes.object,
+ defaultContainer: PropTypes.object,
+ fullContainer: PropTypes.object,
+ updateContainer: PropTypes.func,
+ };
+
+ return AxesConnectedComponent;
+}
diff --git a/src/lib/connectLayoutToPlot.js b/src/lib/connectLayoutToPlot.js
index 3bbe3ea41..00e474c52 100644
--- a/src/lib/connectLayoutToPlot.js
+++ b/src/lib/connectLayoutToPlot.js
@@ -14,12 +14,17 @@ export default function connectLayoutToPlot(WrappedComponent) {
getChildContext() {
const {layout, fullLayout, plotly} = this.context;
- return {
- getValObject: attr =>
+
+ let getValObject;
+ if (plotly) {
+ getValObject = attr =>
plotly.PlotSchema.getLayoutValObject(
fullLayout,
nestedProperty({}, attr).parts
- ),
+ );
+ }
+ return {
+ getValObject,
updateContainer: this.updateContainer,
container: layout,
fullContainer: fullLayout,
@@ -48,7 +53,7 @@ export default function connectLayoutToPlot(WrappedComponent) {
LayoutConnectedComponent.contextTypes = {
layout: PropTypes.object,
fullLayout: PropTypes.object,
- plotly: PropTypes.object.isRequired,
+ plotly: PropTypes.object,
onUpdate: PropTypes.func,
};
diff --git a/src/lib/connectToContainer.js b/src/lib/connectToContainer.js
index 6209b71a5..315c61250 100644
--- a/src/lib/connectToContainer.js
+++ b/src/lib/connectToContainer.js
@@ -5,6 +5,12 @@ import {getDisplayName} from '../lib';
export default function connectToContainer(WrappedComponent) {
class ContainerConnectedComponent extends Component {
+ static modifyPlotProps(props, context, plotProps) {
+ if (WrappedComponent.modifyPlotProps) {
+ WrappedComponent.modifyPlotProps(props, context, plotProps);
+ }
+ }
+
constructor(props, context) {
super(props, context);
@@ -39,9 +45,9 @@ export default function connectToContainer(WrappedComponent) {
);
if (props.isVisible) {
return
;
- } else {
- return null;
}
+
+ return null;
}
}
@@ -51,6 +57,7 @@ export default function connectToContainer(WrappedComponent) {
ContainerConnectedComponent.contextTypes = {
container: PropTypes.object,
+ defaultContainer: PropTypes.object,
fullContainer: PropTypes.object,
getValObject: PropTypes.func,
updateContainer: PropTypes.func,
diff --git a/src/lib/connectTraceToPlot.js b/src/lib/connectTraceToPlot.js
index d581ded89..4d2c8120b 100644
--- a/src/lib/connectTraceToPlot.js
+++ b/src/lib/connectTraceToPlot.js
@@ -20,12 +20,18 @@ export default function connectTraceToPlot(WrappedComponent) {
const trace = data[traceIndex] || {};
const fullTraceIndex = findFullTraceIndex(fullData, traceIndex);
const fullTrace = fullData[fullTraceIndex] || {};
- return {
- getValObject: attr =>
+
+ let getValObject;
+ if (plotly) {
+ getValObject = attr =>
plotly.PlotSchema.getTraceValObject(
fullTrace,
nestedProperty({}, attr).parts
- ),
+ );
+ }
+
+ return {
+ getValObject,
updateContainer: this.updateTrace,
deleteContainer: this.deleteTrace,
container: trace,
@@ -70,7 +76,7 @@ export default function connectTraceToPlot(WrappedComponent) {
TraceConnectedComponent.contextTypes = {
fullData: PropTypes.array,
data: PropTypes.array,
- plotly: PropTypes.object.isRequired,
+ plotly: PropTypes.object,
onUpdate: PropTypes.func,
};
diff --git a/src/lib/constants.js b/src/lib/constants.js
index 76226479f..10093cb01 100644
--- a/src/lib/constants.js
+++ b/src/lib/constants.js
@@ -1,4 +1,4 @@
-export const baseClass = "plotly-editor";
+export const baseClass = 'plotly-editor';
/*
* Control represents multiple settings (like for several axes)
@@ -8,447 +8,414 @@ export const baseClass = "plotly-editor";
* strings, we include a non-printable character (ESC) so it's not something
* people could type.
*/
-export const MIXED_VALUES = "\x1bMIXED_VALUES";
+export const MULTI_VALUED = '\x1bMIXED_VALUES';
// how mixed values are represented in text inputs
-export const MIXED_MODE_VALUE = "-";
-
-/*
-export const CLEAR_WORKSPACE = "WORKSPACE_CLEAR_WORKSPACE";
-
-export const EDIT_MODE = {
- ANALYSIS: "WORKSPACE_EDIT_MODE_ANALYSIS",
- GRAPH: "WORKSPACE_EDIT_MODE_GRAPH",
- STYLE: "WORKSPACE_EDIT_MODE_STYLE",
- SHARE: "WORKSPACE_EDIT_MODE_SHARE",
- EXPORT: "WORKSPACE_EDIT_MODE_EXPORT",
- JSON: "WORKSPACE_EDIT_MODE_JSON",
-};
-
-export const STYLE_MODE = {
- TRACES: "WORKSPACE_STYLE_MODE_TRACES",
- LAYOUT: "WORKSPACE_STYLE_MODE_LAYOUT",
- NOTES: "WORKSPACE_STYLE_MODE_NOTES",
- AXES: "WORKSPACE_STYLE_MODE_AXES",
- LEGEND: "WORKSPACE_STYLE_MODE_LEGEND",
- COLOR_BARS: "WORKSPACE_STYLE_MODE_COLOR_BARS",
- SHAPES: "WORKSPACE_STYLE_MODE_SHAPES",
- MAPBOX_LAYERS: "WORKSPACE_STYLE_MODE_MAPBOX_LAYERS",
- IMAGES: "WORKSPACE_STYLE_MODE_IMAGES",
- MOBILE: "WORKSPACE_STYLE_MODE_MOBILE",
-};
-
-export const GRAPH_MODE = {
- CREATE: "WORKSPACE_GRAPH_MODE_CREATE",
- FILTER: "WORKSPACE_GRAPH_MODE_FILTER",
- GROUPBY: "WORKSPACE_GRAPH_MODE_GROUPBY",
+export const MULTI_VALUED_PLACEHOLDER = '---';
+
+export const multiValueText = {
+ title: 'Multiple Values',
+ text:
+ 'This input has multiple values associated with it. ' +
+ 'Changing this setting will override these custom inputs.',
+ subText:
+ "Common Case: An 'All' tab might display this message " +
+ 'because the X and Y tabs contain different settings.',
};
-export const ADD_PLOT_ID = "WORKSPACE_ADD_PLOT_ID";
-export const ADD_PLOT_SOURCE = "WORKSPACE_ADD_PLOT_SOURCE";
-export const ADD_PLOT_SOURCE_SHARE_KEY = "WORKSPACE_ADD_PLOT_SOURCE_SHARE_KEY";
-export const UPDATE_PLOT_DIRTY = "WORKSPACE_UPDATE_PLOT_DIRTY";
-
-export const ADD_SHARE_FID = "WORKSPACE_ADD_SHARE_FID";
-
-// Spec used to create the EditSidebar Buttons
-export const EDIT_MODE_MENU_ITEMS = [
+export const SYMBOLS = [
{
- mode: EDIT_MODE.GRAPH,
- text: "Graph",
+ value: 'circle',
+ alias: 0,
+ label: 'M5,0A5,5 0 1,1 0,-5A5,5 0 0,1 5,0Z',
+ threeD: true,
+ gl: true,
},
{
- mode: EDIT_MODE.STYLE,
- text: "Style",
+ value: 'circle-open',
+ alias: 0,
+ label: 'M5,0A5,5 0 1,1 0,-5A5,5 0 0,1 5,0Z',
+ fill: 'none',
+ threeD: true,
+ gl: true,
},
{
- mode: EDIT_MODE.ANALYSIS,
- text: "Analysis",
+ value: 'circle-open-dot',
+ alias: 0,
+ label: 'M5,0A5,5 0 1,1 0,-5A5,5 0 0,1 5,0ZM0,0.5L0.5,0L0,-0.5L-0.5,0Z',
+ fill: 'none',
},
+
+ {value: 'square', alias: 1, label: 'M5,5H-5V-5H5Z', threeD: true, gl: true},
{
- mode: EDIT_MODE.JSON,
- text: "JSON",
+ value: 'square-open',
+ alias: 1,
+ label: 'M5,5H-5V-5H5Z',
+ fill: 'none',
+ threeD: true,
+ gl: true,
},
{
- mode: EDIT_MODE.EXPORT,
- text: "Export",
+ value: 'square-open-dot',
+ alias: 1,
+ label: 'M5,5H-5V-5H5ZM0,0.5L0.5,0L0,-0.5L-0.5,0Z',
+ fill: 'none',
},
-];
-export const STYLE_MODE_MENU_ITEMS = [
{
- mode: STYLE_MODE.TRACES,
- text: "Traces",
+ value: 'diamond',
+ alias: 2,
+ label: 'M6.5,0L0,6.5L-6.5,0L0,-6.5Z',
+ threeD: true,
+ gl: true,
},
{
- mode: STYLE_MODE.LAYOUT,
- text: "Layout",
+ value: 'diamond-open',
+ alias: 2,
+ label: 'M6.5,0L0,6.5L-6.5,0L0,-6.5Z',
+ fill: 'none',
+ threeD: true,
+ gl: true,
},
{
- mode: STYLE_MODE.NOTES,
- text: "Notes",
+ value: 'diamond-open-dot',
+ alias: 2,
+ label: 'M6.5,0L0,6.5L-6.5,0L0,-6.5ZM0,0.5L0.5,0L0,-0.5L-0.5,0Z',
+ fill: 'none',
},
+
{
- mode: STYLE_MODE.AXES,
- text: "Axes",
+ value: 'cross',
+ alias: 3,
+ label: 'M6,2H2V6H-2V2H-6V-2H-2V-6H2V-2H6Z',
+ threeD: true,
+ gl: true,
},
{
- mode: STYLE_MODE.LEGEND,
- text: "Legend",
+ value: 'cross-open',
+ alias: 3,
+ label: 'M6,2H2V6H-2V2H-6V-2H-2V-6H2V-2H6Z',
+ fill: 'none',
+ gl: true,
},
+
{
- mode: STYLE_MODE.COLOR_BARS,
- text: "Color Bars",
+ value: 'x',
+ alias: 4,
+ label:
+ 'M0,2.83l2.83,2.83l2.83,-2.83l-2.83,-2.83l2.83,-2.83l-2.83,-2.83l-2.83,2.83l-2.83,-2.83l-2.83,2.83l2.83,2.83l-2.83,2.83l2.83,2.83Z',
+ threeD: true,
+ gl: true,
},
{
- mode: STYLE_MODE.SHAPES,
- text: "Shapes",
+ value: 'x-open',
+ alias: 4,
+ label:
+ 'M0,2.83l2.83,2.83l2.83,-2.83l-2.83,-2.83l2.83,-2.83l-2.83,-2.83l-2.83,2.83l-2.83,-2.83l-2.83,2.83l2.83,2.83l-2.83,2.83l2.83,2.83Z',
+ fill: 'none',
+ gl: true,
},
+
+ {value: 'triangle-up', alias: 5, label: 'M-5.77,2.5H5.77L0,-5Z', gl: true},
{
- mode: STYLE_MODE.MAPBOX_LAYERS,
- text: "GeoJSON",
+ value: 'triangle-up-open',
+ alias: 5,
+ label: 'M-5.77,2.5H5.77L0,-5Z',
+ fill: 'none',
+ gl: true,
},
+
+ {value: 'triangle-down', alias: 6, label: 'M-5.77,-2.5H5.77L0,5Z', gl: true},
{
- mode: STYLE_MODE.IMAGES,
- text: "Images",
+ value: 'triangle-down-open',
+ alias: 6,
+ label: 'M-5.77,-2.5H5.77L0,5Z',
+ fill: 'none',
+ gl: true,
},
+
+ {value: 'triangle-left', alias: 7, label: 'M2.5,-5.77V5.77L-5,0Z', gl: true},
{
- mode: STYLE_MODE.MOBILE,
- text: "Mobile",
+ value: 'triangle-left-open',
+ alias: 7,
+ label: 'M2.5,-5.77V5.77L-5,0Z',
+ fill: 'none',
+ gl: true,
},
-];
-export const GRAPH_MODE_MENU_ITEMS = [
+ {value: 'triangle-right', alias: 8, label: 'M-2.5,-5.77V5.77L5,0Z', gl: true},
{
- mode: GRAPH_MODE.CREATE,
- text: "create",
+ value: 'triangle-right-open',
+ alias: 8,
+ label: 'M-2.5,-5.77V5.77L5,0Z',
+ fill: 'none',
+ gl: true,
},
+
+ {value: 'triangle-ne', alias: 9, label: 'M-6,-3H3V6Z', gl: true},
{
- mode: GRAPH_MODE.FILTER,
- text: "filter",
+ value: 'triangle-ne-open',
+ alias: 9,
+ label: 'M-6,-3H3V6Z',
+ fill: 'none',
+ gl: true,
},
+
+ {value: 'triangle-se', alias: 10, label: 'M3,-6V3H-6Z', gl: true},
{
- mode: GRAPH_MODE.GROUPBY,
- text: "group",
+ value: 'triangle-se-open',
+ alias: 10,
+ label: 'M3,-6V3H-6Z',
+ fill: 'none',
+ gl: true,
},
-];
-
-// temp / saved state of fids and column uids
-export const UPDATE_COLUMN_ID_MAP = "WORKSPACE_UPDATE_COLUMN_ID_MAP";
-export const UPDATE_FID_MAP = "WORKSPACE_UPDATE_FID_MAP";
-export const UPDATE_LAST_SAVED = "WORKSPACE_UPDATE_LAST_SAVED";
-export const MARK_FID_AS_UNSAVED = "WORKSPACE_MARK_FID_AS_UNSAVED";
-export const REMOVE_COLUMN_IDS_FROM_COLUMN_ID_MAP =
- "WORKSPACE_REMOVE_COLUMN_IDS_FROM_COLUMN_ID_MAP";
-
-// panels
-export const EDIT_MODES = Object.keys(EDIT_MODE).map(k => EDIT_MODE[k]);
-export const STYLE_MODES = Object.keys(STYLE_MODE).map(k => STYLE_MODE[k]);
-export const GRAPH_MODES = Object.keys(GRAPH_MODE).map(k => GRAPH_MODE[k]);
-export const SELECT_EDIT_MODE = "WORKSPACE_SELECT_EDIT_MODE";
-export const SELECT_STYLE_MODE = "WORKSPACE_SELECT_STYLE_MODE";
-export const SELECT_GRAPH_MODE = "WORKSPACE_SELECT_GRAPH_MODE";
-
-// figure
-export const ADD_BREAKPOINT = "WORKSPACE_ADD_BREAKPOINT";
-export const DELETE_BREAKPOINT = "WORKSPACE_DELETE_BREAKPOINT";
-export const PLOTLY_RELAYOUT = "WORKSPACE_RELAYOUT";
-export const PLOTLY_RESTYLE = "WORKSPACE_RESTYLE";
-export const PLOTLY_NEW_PLOT = "WORKSPACE_NEW_PLOT";
-export const PLOTLY_ADD_FRAMES = "WORKSPACE_ADD_FRAMES";
-export const PLOTLY_DELETE_FRAMES = "WORKSPACE_DELETE_FRAMES";
-export const SELECT_FRAME = "WORKSPACE_SELECT_FRAME";
-export const SET_BASE_LAYOUT = "WORKSPACE_SET_BASE_LAYOUT";
-export const SET_BREAKPOINT = "WORKSPACE_SET_BREAKPOINT";
-
-// annotations
-export const INLINE_STYLE_LINK = "LINK";
-export const INLINE_STYLE_SUPER = "SUPERSCRIPT";
-export const INLINE_STYLE_SUB = "SUBSCRIPT";
-
-// columns and tables
-export const MERGE_COLUMNS_AND_TABLES = "WORKSPACE_MERGE_COLUMNS_AND_TABLES";
-export const UPDATE_TABLE = "UPDATE_TABLE";
-export const ADD_EMPTY_TABLE = "WORKSPACE_ADD_EMTPY_TABLE";
-export const SELECT_TABLE = "WORKSPACE_SELECT_TABLE";
-export const OVERWRITE_SOURCE = "WORKSPACE_OVERWRITE_SOURCE";
-export const REMOVE_TABLE = "WORKSPACE_REMOVE_TABLE";
-export const REMOVE_COLUMNS_FROM_TABLE = "WORKSPACE_REMOVE_COLUMNS_FROM_TABLE";
-
-// encoding
-export const ASSIGN_COLUMN = "WORKSPACE_ASSIGN_COLUMN";
-export const SWITCH_CHART_TYPE = "WORKSPACE_SWITCH_CHART_TYPE";
-export const NEW_ENCODING_LAYER = "WORKSPACE_NEW_ENCODING_LAYER";
-export const REMOVE_ENCODING_LAYER = "WORKSPACE_REMOVE_ENCODING_LAYER";
-export const SET_ENCODING = "WORKSPACE_SET_ENCODING";
-export const DEFAULT_ENCODING_TYPE = "scatter";
-
-// analyses
-export const UPDATE_ANALYSIS = "WORKSPACE_UPDATE_ANALYSIS";
-export const ADD_ANALYSIS = "WORKSPACE_ADD_ANALYSIS";
-export const REMOVE_ANALYSIS = "WORKSPACE_REMOVE_ANALYSIS";
-export const UPDATE_ANALYSIS_META = "WORKSPACE_UPDATE_ANALYSIS_META";
-
-// HOT
-export const MAX_HOT_ROWS = 5000;
-export const CONTEXT_MENU_SOURCE = "WORKSPACE_CONTEXT_MENU_SOURCE";
-export const SORT_SOURCE = "WORKSPACE_SORT_SOURCE";
-export const INSERT_HEADERS_ABOVE_SOURCE =
- "WORKSPACE_INSERT_HEADERS_ABOVE_SOURCE";
-export const CONTEXT_MENU_KEYS = {
- CLEAR_HEADERS: "WORKSPACE_CONTEXT_MENU_KEYS_CLEAR_HEADERS",
- COL_LEFT: "WORKSPACE_CONTEXT_MENU_KEYS_COL_LEFT",
- COL_RIGHT: "WORKSPACE_CONTEXT_MENU_KEYS_COL_RIGHT",
- INSERT_HEADERS_ABOVE: "WORKSPACE_CONTEXT_MENU_KEYS_INSERT_HEADERS_ABOVE",
- REMOVE_COL: "WORKSPACE_CONTEXT_MENU_KEYS_REMOVE_COL",
- REMOVE_ROW: "WORKSPACE_CONTEXT_MENU_KEYS_REMOVE_ROW",
- RENAME_HEADER: "WORKSPACE_CONTEXT_MENU_KEYS_RENAME_HEADER",
- RESET_SORT: "WORKSPACE_CONTEXT_MENU_KEYS_RESET_SORT",
- ROW_ABOVE: "WORKSPACE_CONTEXT_MENU_KEYS_ROW_ABOVE",
- ROW_BELOW: "WORKSPACE_CONTEXT_MENU_KEYS_ROW_BELOW",
- SET_HEADERS: "WORKSPACE_CONTEXT_MENU_KEYS_SET_HEADERS",
- SORT_ASCENDING: "WORKSPACE_CONTEXT_MENU_KEYS_SORT_ASCENDING",
- SORT_DESCENDING: "WORKSPACE_CONTEXT_MENU_KEYS_SORT_DESCENDING",
- TRANSPOSE_TABLE: "WORKSPACE_CONTEXT_MENU_KEYS_TRANSPOSE_TABLE",
-};
-
-// style panels
-export const CONTROL_TYPES = {
- FONT: "FONT",
- COLOR: "COLOR",
- ANCHOR_SELECTOR: "ANCHOR_SELECTOR",
- DASH: "DASH",
- SYMBOL: "SYMBOL",
- RADIO: "RADIO",
- TEXTAREA: "TEXTAREA",
- ANNOTATION_EDITOR: "ANNOTATION_EDITOR",
- MAPBOX_ACCESS_TOKEN: "MAPBOX_ACCESS_TOKEN",
- NUMERIC_INPUT: "NUMERIC_INPUT",
- FLAGLIST_CHECKBOX: "FLAGLIST_CHECKBOX",
- COLUMN_INPUT: "COLUMN_INPUT",
- COLOR_PALETTE: "COLOR_PALETTE",
- DATETIME_INPUT: "DATETIME_INPUT",
- DATETIME_DURATION: "DATETIME_DURATION",
- DROPDOWN_SELECTOR: "DROPDOWN_SELECTOR",
- DROPDOWN_WITH_TEXT_INPUT: "DROPDOWN_WITH_TEXT_INPUT",
- RANGE: "RANGE",
- SLIDER: "SLIDER",
- INPUT_SLIDER: "INPUT_SLIDER",
- BUTTON: "BUTTON",
- RANGE_SELECTOR_BUTTONS: "RANGE_SELECTOR_BUTTONS",
- TEXT_INPUT: "TEXT_INPUT",
- UPLOAD_SHAPE_FILE: "UPLOAD_SHAPE_FILE",
- UPLOAD_IMAGE_FILE: "UPLOAD_IMAGE_FILE",
- REF_CONTROL: "REF_CONTROL",
- ANCHOR: "ANCHOR",
- MAPBOX_STYLE_URL: "MAPBOX_STYLE_URL",
- ORIENTATION: "ORIENTATION",
- NOTE: "NOTE",
- ARROW: "ARROW",
-};
-
-// encoding panel
-export const ENCODING_ATTRIBUTE_TYPES = {
- PLOTLYJS: "PLOTLYJS",
- ENCODING: "ENCODING",
-};
-
-// import modal
-export const IMPORT_MODES = {
- EXAMPLES: "EXAMPLES",
- SQL: "SQL",
- URL: "URL",
- UPLOAD: "UPLOAD",
-};
-export const ORG_IMPORT_MODES = {
- DATASET: "DATASET",
- NOTEBOOK: "NOTEBOOK",
- PRESENTATION: "PRESENTATION",
-};
-
-export const IMPORT_EXAMPLE_URL =
- "https://raw.githubusercontent.com/plotly/datasets/master/iris.csv";
-
-export const CHART_TYPE_ICON = {
- animation: "icon-animation",
- area: "icon-plot-area",
- bar: "icon-plot-bar",
- box: "icon-plot-box",
- candlestick: "icon-candlestick",
- cartesianArea: "icon-plot-area",
- choropleth: "icon-choropleth",
- contour: "icon-contour",
- errorbars: "icon-error-bars",
- heatmap: "icon-plot-heatmap",
- histogram2d: "icon-plot-2d-hist",
- histogram2dcontour: "icon-plot-2d-hist",
- histogram: "icon-plot-hist",
- line: "icon-plot-line",
- mesh3d: "icon-mesh3d",
- ohlc: "icon-ohlc",
- pie: "icon-pie-chart",
- scatter3d: "icon-plot-3d-scatter",
- line3d: "icon-plot-3d-line",
- scatter: "icon-plot-scatter",
- scattergeo: "icon-scatter-chart",
- scattermapbox: "icon-scatter-chart",
- scatterternary: "icon-ternary-scatter",
- surface: "icon-plot-3d-surface",
- timeseries: "icon-time-series",
-};
+ {value: 'triangle-sw', alias: 11, label: 'M6,3H-3V-6Z', gl: true},
+ {
+ value: 'triangle-sw-open',
+ alias: 11,
+ label: 'M6,3H-3V-6Z',
+ fill: 'none',
+ gl: true,
+ },
-export const ALL_AXES = [
- {
- axisTypeIdentifier: "AxesSpec",
- typeQuery: "cartesian",
- identifier: "xaxis",
- defaultAxis: "xaxis",
- options: [
- { value: "allaxes", label: "All", title: "All Axes" },
- { value: "xaxis", label: "X", title: "X Axes", singular: "X axis" },
- { value: "yaxis", label: "Y", title: "Y Axes", singular: "Y axis" },
- ],
- },
- {
- axisTypeIdentifier: "AxesSpec",
- typeQuery: "gl2d",
- identifier: "xaxis",
- defaultAxis: "xaxis",
- options: [
- { value: "allaxes", label: "All", title: "All Axes" },
- { value: "xaxis", label: "X", title: "X Axes", singular: "X axis" },
- { value: "yaxis", label: "Y", title: "Y Axes", singular: "Y axis" },
- ],
- },
- {
- axisTypeIdentifier: "GeoSpec",
- typeQuery: "geo",
- identifier: "geo",
- defaultAxis: "lonaxis",
- options: [
- { value: "lataxis", label: "Latitude", title: "Latitude" },
- { value: "lonaxis", label: "Longitude", title: "Longitude" },
- ],
- },
- {
- axisTypeIdentifier: "SceneSpec",
- typeQuery: "gl3d",
- identifier: "scene",
- defaultAxis: "xaxis",
- options: [
- { value: "allaxes", label: "All", title: "All Axes" },
- { value: "xaxis", label: "X", title: "X Axes", singular: "X axis" },
- { value: "yaxis", label: "Y", title: "Y Axes", singular: "Y axis" },
- { value: "zaxis", label: "Z", title: "Z Axes", singular: "Z axis" },
- ],
- },
- {
- axisTypeIdentifier: "TernarySpec",
- typeQuery: "ternary",
- identifier: "ternary",
- defaultAxis: "aaxis",
- options: [
- { value: "aaxis", label: "A", title: "A Axes", singular: "A Axis" },
- { value: "baxis", label: "B", title: "B Axes", singular: "B Axis" },
- { value: "caxis", label: "C", title: "C Axes", singular: "C Axis" },
- ],
- },
- {
- typeQuery: "pie",
- options: "NO_AXES",
+ {value: 'triangle-nw', alias: 12, label: 'M-3,6V-3H6Z', gl: true},
+ {
+ value: 'triangle-nw-open',
+ alias: 12,
+ label: 'M-3,6V-3H6Z',
+ fill: 'none',
+ gl: true,
},
-];
-// Layout specification for CategorizedSelectTrace
-export const CHART_CATEGORY = {
- BUSINESS: "BUSINESS",
- SCIENCE: "SCIENCE",
- CHARTS_3D: "CHARTS_3D",
- FINANCIAL: "FINANCIAL",
- STATISTICS: "STATISTICS",
- MAPS: "MAPS",
-};
+ {
+ value: 'pentagon',
+ alias: 13,
+ label: 'M4.76,-1.54L2.94,4.05H-2.94L-4.76,-1.54L0,-5Z',
+ gl: true,
+ },
+ {
+ value: 'pentagon-open',
+ alias: 13,
+ label: 'M4.76,-1.54L2.94,4.05H-2.94L-4.76,-1.54L0,-5Z',
+ fill: 'none',
+ gl: true,
+ },
-export const CATEGORY_LAYOUT = [
- { category: CHART_CATEGORY.BUSINESS, label: "Business" },
- { category: CHART_CATEGORY.SCIENCE, label: "Science" },
- { category: CHART_CATEGORY.CHARTS_3D, label: "3d charts" },
- { category: CHART_CATEGORY.FINANCIAL, label: "Finance" },
- { category: CHART_CATEGORY.STATISTICS, label: "Statistics" },
- { category: CHART_CATEGORY.MAPS, label: "Maps" },
-];
+ {
+ value: 'hexagon',
+ alias: 14,
+ label: 'M4.33,-2.5V2.5L0,5L-4.33,2.5V-2.5L0,-5Z',
+ gl: true,
+ },
+ {
+ value: 'hexagon-open',
+ alias: 14,
+ label: 'M4.33,-2.5V2.5L0,5L-4.33,2.5V-2.5L0,-5Z',
+ fill: 'none',
+ gl: true,
+ },
-// ShareModalTabs
-export const SHARE_MODAL_TAB_OPTIONS = {
- LINK_AND_PRIVACY: "Link & Privacy",
- COLLABORATORS: "Collaborate",
- EMBED: "Embed",
-};
+ {
+ value: 'hexagon2',
+ alias: 15,
+ label: 'M-2.5,4.33H2.5L5,0L2.5,-4.33H-2.5L-5,0Z',
+ gl: true,
+ },
+ {
+ value: 'hexagon2-open',
+ alias: 15,
+ label: 'M-2.5,4.33H2.5L5,0L2.5,-4.33H-2.5L-5,0Z',
+ fill: 'none',
+ gl: true,
+ },
-export const SHARE_MODAL_TABS = Object.keys(SHARE_MODAL_TAB_OPTIONS).map(k => {
- return SHARE_MODAL_TAB_OPTIONS[k];
-});
+ {
+ value: 'octagon',
+ alias: 16,
+ label:
+ 'M-1.92,-4.62H1.92L4.62,-1.92V1.92L1.92,4.62H-1.92L-4.62,1.92V-1.92Z',
+ },
+ {
+ value: 'octagon-open',
+ alias: 16,
+ label:
+ 'M-1.92,-4.62H1.92L4.62,-1.92V1.92L1.92,4.62H-1.92L-4.62,1.92V-1.92Z',
+ fill: 'none',
+ },
-// 1x1px transparent gif: https://css-tricks.com/snippets/html/base64-encode-of-1x1px-transparent-gif/
-export const IMAGE_PLACEHOLDER =
- "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
+ {
+ value: 'star',
+ alias: 17,
+ label:
+ 'M1.58,-2.16H6.66L2.54,0.83L4.12,5.66L0,2.67L-4.12,5.66L-2.54,0.83L-6.66,-2.16H-1.58L0,-7Z',
+ gl: true,
+ },
+ {
+ value: 'star-open',
+ alias: 17,
+ label:
+ 'M1.58,-2.16H6.66L2.54,0.83L4.12,5.66L0,2.67L-4.12,5.66L-2.54,0.83L-6.66,-2.16H-1.58L0,-7Z',
+ fill: 'none',
+ gl: true,
+ },
-export const MAPBOX_ERROR_TYPES = {
- INVALID_JSON: "INVALID_JSON",
- FAILED_REQUEST: "FAILED_REQUEST",
- FAILED_PARSING: "FAILED_PARSING",
- UNKNOWN: "UNKNOWN",
-};
+ {
+ value: 'hexagram',
+ alias: 18,
+ label:
+ 'M-3.8,0l-1.9,-3.3h3.8l1.9,-3.3l1.9,3.3h3.8l-1.9,3.3l1.9,3.3h-3.8l-1.9,3.3l-1.9,-3.3h-3.8Z',
+ },
+ {
+ value: 'hexagram-open',
+ alias: 18,
+ label:
+ 'M-3.8,0l-1.9,-3.3h3.8l1.9,-3.3l1.9,3.3h3.8l-1.9,3.3l1.9,3.3h-3.8l-1.9,3.3l-1.9,-3.3h-3.8Z',
+ fill: 'none',
+ },
-export const WORKSPACE_PLOT_ID = "js-main-plotly-workspace-plot";
+ {
+ value: 'star-triangle-up',
+ alias: 19,
+ label:
+ 'M-6.93,4A 20,20 0 0 1 6.93,4A 20,20 0 0 1 0,-8A 20,20 0 0 1 -6.93,4Z',
+ },
+ {
+ value: 'star-triangle-up-open',
+ alias: 19,
+ label:
+ 'M-6.93,4A 20,20 0 0 1 6.93,4A 20,20 0 0 1 0,-8A 20,20 0 0 1 -6.93,4Z',
+ fill: 'none',
+ },
-export const WORKSPACE_CONTAINER = document.getElementById("main");
-export const WORKSPACE_PLACEHOLDER = document.getElementById(
- "placeholderworkspace"
-);
+ {
+ value: 'star-triangle-down',
+ alias: 20,
+ label:
+ 'M6.93,-4A 20,20 0 0 1 -6.93,-4A 20,20 0 0 1 0,8A 20,20 0 0 1 6.93,-4Z',
+ },
+ {
+ value: 'star-triangle-down-open',
+ alias: 20,
+ label:
+ 'M6.93,-4A 20,20 0 0 1 -6.93,-4A 20,20 0 0 1 0,8A 20,20 0 0 1 6.93,-4Z',
+ fill: 'none',
+ },
-const IS_TRANSFORM = true;
+ {
+ value: 'star-square',
+ alias: 21,
+ label:
+ 'M-5.5,-5.5A 10,10 0 0 1 -5.5,5.5A 10,10 0 0 1 5.5,5.5A 10,10 0 0 1 5.5,-5.5A 10,10 0 0 1 -5.5,-5.5Z',
+ },
+ {
+ value: 'star-square-open',
+ alias: 21,
+ label:
+ 'M-5.5,-5.5A 10,10 0 0 1 -5.5,5.5A 10,10 0 0 1 5.5,5.5A 10,10 0 0 1 5.5,-5.5A 10,10 0 0 1 -5.5,-5.5Z',
+ fill: 'none',
+ },
-// quadruplet containing [CONSTANT_NAME TYPE LABEL IS_TRANSFORM].
-export const ANALYSES = [
- [
- "DESCRIPTIVE",
- "descriptive-statistics",
- "Descriptive statistics",
- !IS_TRANSFORM,
- ],
- ["ANOVA_TEST", "anova", "ANOVA", !IS_TRANSFORM],
- ["CHI_SQUARED_TEST", "chi-squared-test", "Chi-squared test", !IS_TRANSFORM],
- ["T_TEST", "t-test", "T-test (two-tailed, independent)", !IS_TRANSFORM],
- ["CORRELATION", "column-correlation", "Column correlation", !IS_TRANSFORM],
- ["FIT", "fit", "Curve fitting", IS_TRANSFORM],
- ["AVERAGE", "average", "Average", IS_TRANSFORM],
- ["MOVING_AVERAGE", "moving-average", "Moving average", IS_TRANSFORM],
-];
+ {
+ value: 'star-diamond',
+ alias: 22,
+ label:
+ 'M-7,0A 9.5,9.5 0 0 1 0,7A 9.5,9.5 0 0 1 7,0A 9.5,9.5 0 0 1 0,-7A 9.5,9.5 0 0 1 -7,0Z',
+ },
+ {
+ value: 'star-diamond-open',
+ alias: 22,
+ label:
+ 'M-7,0A 9.5,9.5 0 0 1 0,7A 9.5,9.5 0 0 1 7,0A 9.5,9.5 0 0 1 0,-7A 9.5,9.5 0 0 1 -7,0Z',
+ fill: 'none',
+ },
-export const ANALYSES_TYPES = ANALYSES.reduce((accum, [name, type]) => {
- accum[name] = type;
- return accum;
-}, {});
+ {
+ value: 'diamond-tall',
+ alias: 23,
+ label: 'M0,7L3.5,0L0,-7L-3.5,0Z',
+ gl: true,
+ },
+ {
+ value: 'diamond-tall-open',
+ alias: 23,
+ label: 'M0,7L3.5,0L0,-7L-3.5,0Z',
+ fill: 'none',
+ gl: true,
+ },
-export const ANALYSES_TYPES_TO_LABELS = ANALYSES.reduce(
- (accum, [, type, label]) => {
- accum[type] = label;
- return accum;
+ {value: 'diamond-wide', alias: 24, label: 'M0,3.5L7,0L0,-3.5L-7,0Z'},
+ {
+ value: 'diamond-wide-open',
+ alias: 24,
+ label: 'M0,3.5L7,0L0,-3.5L-7,0Z',
+ fill: 'none',
},
- {}
-);
-// used by WorkspaceActions to search for linked transforms inside traces
-export const TRANSFORM_TYPES = ANALYSES.filter(
- ([, , , isTransform]) => isTransform
-).map(([, type]) => type);
+ {value: 'hourglass', alias: 25, label: 'M5,5H-5L5,-5H-5Z'},
+ {value: 'bowtie', alias: 26, label: 'M5,5V-5L-5,5V-5Z', gl: true},
+ {
+ value: 'cross-thin-open',
+ alias: 33,
+ label: 'M0,7V-7M7,0H-7',
+ fill: 'none',
+ gl: true,
+ },
+ {
+ value: 'x-thin-open',
+ alias: 34,
+ label: 'M5,5L-5,-5M5,-5L-5,5',
+ fill: 'none',
+ },
+ {
+ value: 'asterisk-open',
+ alias: 35,
+ label: 'M0,6V-6M6,0H-6M4.25,4.25L-4.25,-4.25M4.25,-4.25L-4.25,4.25',
+ fill: 'none',
+ gl: true,
+ },
-// Constants relating to the user interface
+ {
+ value: 'hash-open',
+ alias: 36,
+ label: 'M2.5,5V-5m-5,0V5M5,2.5H-5m0,-5H5',
+ fill: 'none',
+ },
+ {
+ value: 'hash-open-dot',
+ alias: 36,
+ label: 'M2.5,5V-5m-5,0V5M5,2.5H-5m0,-5H5M0,0.5L0.5,0L0,-0.5L-0.5,0Z',
+ fill: 'none',
+ },
-export const RETURN_KEY = "Enter";
-export const ESCAPE_KEY = "Escape";
-export const COMMAND_KEY = "Meta";
-export const CONTROL_KEY = "Control";
-*/
+ {
+ value: 'y-up-open',
+ alias: 37,
+ label: 'M-6,4L0,0M6,4L0,0M0,-8L0,0',
+ fill: 'none',
+ gl: true,
+ },
+ {
+ value: 'y-down-open',
+ alias: 38,
+ label: 'M-6,-4L0,0M6,-4L0,0M0,8L0,0',
+ fill: 'none',
+ gl: true,
+ },
+ {
+ value: 'y-left-open',
+ alias: 39,
+ label: 'M4,6L0,0M4,-6L0,0M-8,0L0,0',
+ fill: 'none',
+ },
+ {
+ value: 'y-right-open',
+ alias: 40,
+ label: 'M-4,6L0,0M-4,-6L0,0M8,0L0,0',
+ fill: 'none',
+ },
+ {value: 'line-ew-open', alias: 41, label: 'M7,0H-7', fill: 'none', gl: true},
+ {value: 'line-ns-open', alias: 42, label: 'M0,7V-7', fill: 'none', gl: true},
+ {value: 'line-ne-open', alias: 43, label: 'M5,-5L-5,5', fill: 'none'},
+ {value: 'line-nw-open', alias: 44, label: 'M5,5L-5,-5', fill: 'none'},
+];
diff --git a/src/lib/index.js b/src/lib/index.js
index 91359c36d..3de73e8f7 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -1,11 +1,12 @@
import bem, {icon} from './bem';
+import connectAxesToLayout from './connectAxesToLayout';
import connectLayoutToPlot from './connectLayoutToPlot';
import connectToContainer from './connectToContainer';
import connectTraceToPlot from './connectTraceToPlot';
import dereference from './dereference';
import findFullTraceIndex from './findFullTraceIndex';
import localize, {localizeString} from './localize';
-import walkObject, {makeAttrSetterPath} from './walkObject';
+import walkObject, {isPlainObject} from './walkObject';
function clamp(value, min, max) {
return Math.max(min, Math.min(max, value));
@@ -18,6 +19,7 @@ function getDisplayName(WrappedComponent) {
export {
bem,
clamp,
+ connectAxesToLayout,
connectLayoutToPlot,
connectToContainer,
connectTraceToPlot,
@@ -25,8 +27,8 @@ export {
getDisplayName,
findFullTraceIndex,
icon,
+ isPlainObject,
localize,
localizeString,
- makeAttrSetterPath,
walkObject,
};
diff --git a/src/lib/unpackPlotProps.js b/src/lib/unpackPlotProps.js
index 197a321c3..1d53e5b0f 100644
--- a/src/lib/unpackPlotProps.js
+++ b/src/lib/unpackPlotProps.js
@@ -1,55 +1,82 @@
import nestedProperty from 'plotly.js/src/lib/nested_property';
import isNumeric from 'fast-isnumeric';
-import findFullTraceIndex from './findFullTraceIndex';
+import {MULTI_VALUED, MULTI_VALUED_PLACEHOLDER} from './constants';
export default function unpackPlotProps(props, context, ComponentClass) {
- const {updateContainer, container, fullContainer} = context;
+ const {
+ container,
+ getValObject,
+ defaultContainer,
+ fullContainer,
+ updateContainer,
+ } = context;
if (!container || !fullContainer) {
throw new Error(
- `${ComponentClass.name} must be nested within a
, ` +
- 'or container'
+ `${ComponentClass.name} must be nested within a component connected ` +
+ 'to a plotly.js container.'
);
}
// Property accessors and meta information:
const fullProperty = nestedProperty(fullContainer, props.attr);
- const property = nestedProperty(container, props.attr);
- const fullValue = () => fullProperty.get();
+ const fullValue = () => {
+ const fv = fullProperty.get();
+ if (fv === MULTI_VALUED) {
+ return MULTI_VALUED_PLACEHOLDER;
+ }
+ return fv;
+ };
+
+ let defaultValue = props.defaultValue;
+ if (defaultValue === void 0 && defaultContainer) {
+ defaultValue = nestedProperty(defaultContainer, props.attr).get();
+ }
// Property descriptions and meta:
- const attrMeta = context.getValObject(props.attr) || {};
+ let attrMeta;
+ if (getValObject) {
+ attrMeta = context.getValObject(props.attr) || {};
+ }
// Update data functions:
const updatePlot = v => updateContainer && updateContainer({[props.attr]: v});
- // Visibility:
+ // Visibility and multiValues:
+ const fv = fullProperty.get();
+ const multiValued = fv === MULTI_VALUED;
+
let isVisible = false;
- const fv = fullValue();
- if (props.show || (fv !== undefined && fv !== null)) {
+ if (props.show || (fv !== void 0 && fv !== null)) {
isVisible = true;
}
const plotProps = {
attrMeta,
container,
+ defaultValue,
+ getValObject,
fullContainer,
fullValue,
isVisible,
updateContainer,
updatePlot,
+ multiValued,
};
- if (isNumeric(attrMeta.max)) {
- plotProps.max = attrMeta.max;
- }
- if (isNumeric(attrMeta.min)) {
- plotProps.min = attrMeta.min;
+ if (attrMeta) {
+ if (isNumeric(attrMeta.max)) {
+ plotProps.max = attrMeta.max;
+ }
+ if (isNumeric(attrMeta.min)) {
+ plotProps.min = attrMeta.min;
+ }
}
- // Allow Component Classes to further augment plotProps:
- ComponentClass.unpackPlotProps &&
- ComponentClass.unpackPlotProps(props, context, plotProps);
+ // Give Component Classes the space to modify plotProps:
+ if (ComponentClass.modifyPlotProps) {
+ ComponentClass.modifyPlotProps(props, context, plotProps);
+ }
return plotProps;
}
diff --git a/src/lib/walkObject.js b/src/lib/walkObject.js
index 2304defda..64edfea16 100644
--- a/src/lib/walkObject.js
+++ b/src/lib/walkObject.js
@@ -1,4 +1,4 @@
-function isPlainObject(input) {
+export function isPlainObject(input) {
return input && !Array.isArray(input) && typeof input === 'object';
}
diff --git a/src/shame.js b/src/shame.js
new file mode 100644
index 000000000..d6936d532
--- /dev/null
+++ b/src/shame.js
@@ -0,0 +1,12 @@
+/*
+ * DELETE THIS FILE AND EVERYTHING IN IT.
+ */
+import {list} from 'plotly.js/src/plots/cartesian/axis_ids';
+
+export function noShame(opts) {
+ if (opts.plotly && !opts.plotly.Axes) {
+ opts.plotly.Axes = {
+ list,
+ };
+ }
+}
diff --git a/src/styles/_helpers.scss b/src/styles/_helpers.scss
index 749b9cdce..80b462bc2 100644
--- a/src/styles/_helpers.scss
+++ b/src/styles/_helpers.scss
@@ -1,3 +1,3 @@
.\+flex { display: flex; }
.\+cursor-clickable { cursor: pointer; }
-.\+hover-grey:hover{ color: $dark-grey; }
+.\+hover-grey:hover{ color: $color-active-text; }
diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss
index 03e1800af..c633e6ef9 100644
--- a/src/styles/_mixins.scss
+++ b/src/styles/_mixins.scss
@@ -21,3 +21,15 @@
@error "'#{$value}' is not a valid z-index value";
}
}
+
+@mixin clearfix() {
+ &:before,
+ &:after {
+ content: " ";
+ display: table;
+ }
+
+ &:after {
+ clear: both;
+ }
+}
diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss
deleted file mode 100644
index ef82f1079..000000000
--- a/src/styles/_variables.scss
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * SIZING AND WEIGHTS
- */
-$font-weight-light: 300;
-$font-weight-semibold: 600;
-$font-size-small: 12px;
-$h5-size: 16px !default;
-
-/*
- * SPACING
- */
-$base-spacing-unit: 24px !default;
-$half-spacing-unit: 12px;
-$quarter-spacing-unit: 6px;
-$sixth-spacing-unit: 4px;
-$eighth-spacing-unit: 3px;
-
-/*
- * BORDERS
- */
-$border-radius: 2px;
-$border-radius-5: 5px;
-
-/*
- * COLORS
- */
-$color-white: #ffffff;
-$lightgrey: #dcdddd;
-$color-muted-text: #777;
-$dark-grey: #4c5059;
-$color-page-background: #f3f6fa;
-$color-highlight-darker: #bec8d9;
-
-/*
- * COMPONENT
- */
-$fold-border: 1px solid $color-highlight-darker;
diff --git a/src/styles/components/containers/_fold.scss b/src/styles/components/containers/_fold.scss
index 4d1616e77..c91f1f9a8 100644
--- a/src/styles/components/containers/_fold.scss
+++ b/src/styles/components/containers/_fold.scss
@@ -15,8 +15,8 @@
clear: both;
padding: 6px 12px;
color: #ffffff;
- font-size: 13px;
- line-height: 13px;
+ font-size: $font-size;
+ line-height: $font-size;
border: 1px solid #a2b1c6;
background-color: #a2b1c6;
height: 15px;
diff --git a/src/styles/components/containers/_info.scss b/src/styles/components/containers/_info.scss
new file mode 100644
index 000000000..812e6c2cf
--- /dev/null
+++ b/src/styles/components/containers/_info.scss
@@ -0,0 +1,23 @@
+.info__title {
+ color: $color-link-light;
+ font-size: $font-size-medium;
+ font-weight: $font-weight-normal;
+ line-height: 18px;
+ padding: $half-spacing-unit $half-spacing-unit $quarter-spacing-unit $half-spacing-unit;
+}
+
+.info__text {
+ padding: $quarter-spacing-unit $half-spacing-unit;
+ color: $color-secondary-text;
+ font-size: $font-size-small;
+ font-weight: $font-weight-light;
+ line-height: 18px;
+}
+
+.info__sub-text {
+ color: $color-brand;
+ font-size: $font-size-small;
+ font-weight: $font-weight-light;
+ line-height: 14px;
+ padding: $quarter-spacing-unit $half-spacing-unit $half-spacing-unit $half-spacing-unit;
+}
diff --git a/src/styles/components/containers/_main.scss b/src/styles/components/containers/_main.scss
index 34bd1a01f..93140140e 100644
--- a/src/styles/components/containers/_main.scss
+++ b/src/styles/components/containers/_main.scss
@@ -1,3 +1,6 @@
@import "panel";
@import "fold";
@import "section";
+@import "menupanel";
+@import "info";
+@import "modalbox";
diff --git a/src/styles/components/containers/_menupanel.scss b/src/styles/components/containers/_menupanel.scss
new file mode 100644
index 000000000..34992627b
--- /dev/null
+++ b/src/styles/components/containers/_menupanel.scss
@@ -0,0 +1,21 @@
+.menupanel {
+ padding-top: 0;
+}
+
+.menupanel--ownline {
+ padding-top: 6px;
+}
+
+.menupanel__icon-span {
+ font-size: $font-size-small;
+}
+
+.menupanel__icon-span--question {
+ color: $color-link-light;
+}
+
+.menupanel__icon {
+ padding-left: 6px;
+ padding-right: 6px;
+ vertical-align: middle;
+}
diff --git a/src/styles/components/containers/_modalbox.scss b/src/styles/components/containers/_modalbox.scss
new file mode 100644
index 000000000..cd1f7faad
--- /dev/null
+++ b/src/styles/components/containers/_modalbox.scss
@@ -0,0 +1,27 @@
+.modalbox {
+ position: absolute;
+ border-radius: 2px;
+ text-transform: none;
+ text-align: left;
+ border: 1px solid $color-border;
+
+ align-content: center;
+ box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.2);
+ left: 0;
+ right: 0;
+
+ margin: 0 24px;
+ min-width: 200px;
+ background-color: $color-white;
+
+ @include z-index("cloud");
+}
+
+.modalbox__cover {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ @include z-index("underground");
+}
diff --git a/src/styles/components/containers/_panel.scss b/src/styles/components/containers/_panel.scss
index f707b35ce..e1c87e408 100644
--- a/src/styles/components/containers/_panel.scss
+++ b/src/styles/components/containers/_panel.scss
@@ -5,43 +5,9 @@
bottom: 5px;
right: 1px;
margin-left: 100px;
- background-color: $color-page-background;
+ background-color: $color-panel-background;
overflow-y: scroll;
padding-left: $half-spacing-unit;
padding-right: $half-spacing-unit;
padding-top: $half-spacing-unit;
}
-
-.subpanel__toggle {
- padding-left: 12px;
- padding-right: 12px;
- vertical-align: middle;
-}
-
-.subpanel{
- position: absolute;
- border-radius: 2px;
- text-transform: none;
- text-align: left;
- border: $fold-border;
-
- align-content: center;
- box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.2);
- left: 0;
- right: 0;
-
- margin: 0 24px;
- min-width: 200px;
- background-color: $color-white;
-
- @include z-index("cloud");
-}
-
-.subpanel__cover {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- @include z-index("underground");
-}
diff --git a/src/styles/components/containers/_section.scss b/src/styles/components/containers/_section.scss
index ff736cba1..d85c61b17 100644
--- a/src/styles/components/containers/_section.scss
+++ b/src/styles/components/containers/_section.scss
@@ -1,9 +1,10 @@
.section__heading {
- font-size: 13px;
- color: #69738a;
+ display: flex;
+ font-size: $font-size;
+ color: $color-header-text;
font-weight: 400;
cursor: default;
- background-color: #F4FAFF;
+ background-color: $color-section-header-background;
padding: 6px 12px;
clear: both;
text-transform: capitalize;
diff --git a/src/styles/components/fields/_field.scss b/src/styles/components/fields/_field.scss
new file mode 100644
index 000000000..ef5a68dd0
--- /dev/null
+++ b/src/styles/components/fields/_field.scss
@@ -0,0 +1,59 @@
+.field {
+ align-items: center;
+ border-top: 1px solid #f8f8f9;
+ color: #6D6D6D;
+ display: flex;
+ font-size: $font-size;
+ font-weight: $font-weight-light;
+ justify-content: flex-start;
+ line-height: $font-size;
+ min-height: 32px;
+ padding: 6px 0;
+ width: 100%;
+}
+
+.field:not(:last-child) {
+ border-bottom: 1px solid #f8f8f9;
+}
+
+.field__no-title {
+ width: 100%;
+ padding: 0 12px;
+}
+
+.field__no-title--center {
+ text-align: center;
+}
+
+.field__title {
+ padding-left: 0.5em;
+ color: #69738a;
+ font-size: 14px;
+ width: 36%;
+ display: inline-block;
+}
+
+.field__widget {
+ width: 64%;
+ padding-left: 12px;
+ display: inline-block;
+ padding-right: 12px;
+}
+
+.field__widget--postfix {
+ width: 50%;
+ padding-right: 0px;
+}
+
+.field__title {
+ width: 30%;
+ padding-left: 12px;
+ display: inline-block;
+ line-height: 18px;
+ text-transform: capitalize;
+}
+
+.field__title-text {
+ text-transform: capitalize;
+ cursor: default;
+}
diff --git a/src/styles/components/fields/_main.scss b/src/styles/components/fields/_main.scss
index c55ebee7d..3c7187a9b 100644
--- a/src/styles/components/fields/_main.scss
+++ b/src/styles/components/fields/_main.scss
@@ -1,59 +1,2 @@
-.field {
- Align-items: center;
- border-top: 1px solid #f8f8f9;
- color: #6D6D6D;
- display: flex;
- font-size: 13px;
- font-weight: 300;
- justify-content: flex-start;
- line-height: 13px;
- min-height: 32px;
- padding: 6px 0;
- width: 100%;
-}
-
-.field:not(:last-child) {
- border-bottom: 1px solid #f8f8f9;
-}
-
-.field__no-title {
- width: 100%;
- padding: 0 12px;
-}
-
-.field__no-title--center {
- text-align: center;
-}
-
-.field__title {
- padding-left: 0.5em;
- color: #69738a;
- font-size: 14px;
- width: 36%;
- display: inline-block;
-}
-
-.field__widget {
- width: 64%;
- padding-left: 18px;
- display: inline-block;
- padding-right: 12px;
-}
-
-.field__widget--postfix {
- width: 50%;
- padding-right: 0px;
-}
-
-.field__title {
- width: 30%;
- padding-left: 12px;
- display: inline-block;
- line-height: 18px;
- text-transform: capitalize;
-}
-
-.field__title-text {
- text-transform: capitalize;
- cursor: default;
-}
+@import 'field';
+@import 'symbolselector';
diff --git a/src/styles/components/fields/_symbolselector.scss b/src/styles/components/fields/_symbolselector.scss
new file mode 100644
index 000000000..029ae848e
--- /dev/null
+++ b/src/styles/components/fields/_symbolselector.scss
@@ -0,0 +1,38 @@
+.symbol-selector__toggle {
+ border: 1px solid $color-border;
+ border-radius: 4px;
+ width: 80px;
+ cursor: pointer;
+ padding: $quarter-spacing-unit 10px;
+ @include clearfix();
+}
+
+.symbol-selector__toggle__option {
+ float: left;
+}
+
+.symbol-selector__toggle__caret {
+ float: right;
+ color: $color-secondary-text;
+}
+
+.symbol-selector__menu {
+ max-width: 225px;
+ position: absolute;
+ @include z-index("orbit");
+ border: 1px solid $color-border;
+ padding: $font-size;
+ box-shadow: 2px 2px $font-size $color-border-secondary;
+border-radius: $border-radius;
+left: $base-spacing-unit;
+}
+
+ .symbol-selector__item {
+ display: inline;
+ }
+
+ .symbol-selector__symbol {
+ &:hover {
+ background-color: $color-border;
+ }
+ }
diff --git a/src/styles/components/widgets/_colorpicker.scss b/src/styles/components/widgets/_colorpicker.scss
index 0a9244c43..05f762586 100644
--- a/src/styles/components/widgets/_colorpicker.scss
+++ b/src/styles/components/widgets/_colorpicker.scss
@@ -1,6 +1,5 @@
$swatch-size: 20px;
$colorpicker-width: 250px;
-$colorpicker-border-color: #bbb;
$saturation-picker-height: 160px;
$slider-picker-height: 10px;
@@ -43,7 +42,7 @@ $slider-picker-height: 10px;
width: 32px;
height: 32px;
border-radius: 50%;
- border: 1px solid $lightgrey;
+ border: 1px solid $color-border-secondary;
vertical-align: middle;
padding-top: $sixth-spacing-unit;
padding-left: $sixth-spacing-unit;
@@ -52,7 +51,7 @@ $slider-picker-height: 10px;
.colorpicker__selected-color {
margin-left: $half-spacing-unit;
- color: $color-muted-text;
+ color: $color-text;
font-weight: $font-weight-light;
font-size: $font-size-small;
display: inline-block;
@@ -67,17 +66,11 @@ $slider-picker-height: 10px;
vertical-align: middle;
}
-.toolLabel {
- color: $color-muted-text;
- font-size: $font-size-small;
- font-weight: $font-weight-light;
-}
-
%colorpicker__component {
position: relative;
overflow: hidden;
margin: 0 $half-spacing-unit;
- border: 1px solid $colorpicker-border-color;
+ border: 1px solid $color-border;
border-radius: $border-radius / 2;
cursor: pointer;
}
diff --git a/src/styles/components/widgets/_numeric-input.scss b/src/styles/components/widgets/_numeric-input.scss
index 8b9a4b066..853bf29c7 100644
--- a/src/styles/components/widgets/_numeric-input.scss
+++ b/src/styles/components/widgets/_numeric-input.scss
@@ -5,7 +5,7 @@
.numeric-input__number {
display: inline-block;
- border: 1px solid #bec8d9;
+ border: 1px solid $color-border;
background: white;
cursor: text;
overflow: hidden;
@@ -14,7 +14,7 @@
text-align: left;
border-radius: 2px;
padding: 6px 6px 6px 12px;
- width: 80px;
+ width: 62px;
vertical-align: middle;
font-size: inherit;
color: inherit;
@@ -39,8 +39,8 @@
background-color: #f8f8f9;
border: 1px solid #bec8d9;
border-radius: 1px;
- width: 13px;
- height: 13px;
+ width: $font-size;
+ height: $font-size;
line-height: 12px;
text-align: center;
}
diff --git a/src/styles/icons/_icons.scss b/src/styles/icons/_icons.scss
index 4d5405727..359fc8139 100644
--- a/src/styles/icons/_icons.scss
+++ b/src/styles/icons/_icons.scss
@@ -1,6 +1,6 @@
@font-face {
font-family: "react-plotlyjs-editor";
- src: url('data:font/truetype;charset=utf-8;base64,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') format("truetype");
+ src: url('data:font/truetype;charset=utf-8;base64,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') format("truetype");
font-weight: normal;
font-style: normal;
}
@@ -34,3 +34,12 @@
.plotlyjs_editor__icon-cog:before {
content: "\61";
}
+.plotlyjs_editor__icon-question-circle:before {
+ content: "\62";
+}
+.plotlyjs_editor__.icon-caret-down:before {
+ content: "\63";
+}
+.plotlyjs_editor__.icon-angle-down:before {
+ content: "\64";
+}
diff --git a/src/styles/main.scss b/src/styles/main.scss
index e84c6aa38..69b3c3c8e 100644
--- a/src/styles/main.scss
+++ b/src/styles/main.scss
@@ -1,5 +1,5 @@
@charset 'utf-8';
-@import 'variables';
+@import 'variables/main';
@import 'mixins';
@import 'helpers';
@import 'icons/main';
diff --git a/src/styles/variables/_defaults.scss b/src/styles/variables/_defaults.scss
new file mode 100644
index 000000000..15971946e
--- /dev/null
+++ b/src/styles/variables/_defaults.scss
@@ -0,0 +1,90 @@
+/*
+ * SIZING AND WEIGHTS
+ */
+$default-font-weight-light: 300;
+$default-font-weight-normal: 400;
+$default-font-weight-semibold: 600;
+$default-font-size: 13px;
+$default-font-size-small: 12px;
+$default-font-size-medium: 14px;
+$default-h5-size: 16px !default;
+
+/*
+ * SPACING
+ */
+$default-base-spacing-unit: 24px !default;
+$default-half-spacing-unit: 12px;
+$default-quarter-spacing-unit: 6px;
+$default-sixth-spacing-unit: 4px;
+$default-eighth-spacing-unit: 3px;
+
+/*
+ * BORDERS
+ */
+$default-border-radius: 2px;
+$default-border-radius-5: 5px;
+
+/*
+ * COLORS
+ */
+$default-color-black-dark: #000;
+$default-color-black-paleish: #333;
+$default-color-black-pale: #666;
+$default-color-black: #111;
+$default-color-navyblue: #284576;
+$default-color-blue: #119DFF;
+$default-color-blue-dark: darken($default-color-blue, 20%);
+$default-color-blue-pale: #45a9ff;
+$default-color-blue-paleish: #2391fe;
+$default-color-cyan-pale: #9bd1ff;
+$default-color-gold: #f7bd0c;
+$default-color-gray-blue: #69738a;
+$default-color-gray-blue-pale: #bec8d9;
+$default-color-gray-dark: #888;
+$default-color-gray-light: #ddd;
+$default-color-gray: #ccc;
+$default-color-green-cyan: #53cf9d;
+$default-color-green-pale: #dff0d8;
+$default-color-green: #3c763d;
+$default-color-red-pale: #f2dede;
+$default-color-red: #a94442;
+$default-color-white-blue: #f4faff;
+$default-color-white-dark: #eee;
+$default-color-white-darkish: #f8f8f8;
+$default-color-white: #ffffff;
+
+// ---
+// Text and Backgrounds
+// ---
+$default-color-rhino-core: #2a3f5f; // Active state text, emphasized text
+$default-color-rhino-dark: #506784; // Default text color
+$default-color-rhino-medium-1: #a2b1c6;
+$default-color-rhino-medium-2: #c8d4e3; // Border Default
+$default-color-rhino-light-1: #dfe8f3; // Border Secondary
+$default-color-rhino-light-2: #ebf0f8; // Button Secondary
+$default-color-rhino-light-3: #f3f6fa; // Page Background
+$default-color-rhino-light-4: #fafbfd; // Modal Tabbed Content Background
+
+// ---
+// Primary
+// --
+$default-color-dodger: #119DFF; // Accent
+$default-color-dodger-shade: #0D76BF; // Link Hover
+
+// ---
+// Accent
+// ---
+$default-color-aqua: #09ffff;
+$default-color-aqua-shade: #19d3f3; // Body Text
+$default-color-lavender: #e763fa;
+$default-color-lavender-shade: #ab63fa;
+$default-color-cornflower: #636efa;
+$default-color-emerald: #00cc96; // CTA's on blue backgrounds
+$default-color-sienna: #ef553b; // CTA's on blue backgrounds
+
+// ---
+// Buttons
+// ---
+$default-color-button-default: #119dff;
+$default-color-button-hover: #0f89df;
+$default-color-button-active: #0d76bf;
diff --git a/src/styles/variables/_main.scss b/src/styles/variables/_main.scss
new file mode 100644
index 000000000..d62a1ea89
--- /dev/null
+++ b/src/styles/variables/_main.scss
@@ -0,0 +1,51 @@
+@import 'defaults';
+
+
+$font-weight-light: $default-font-weight-light;
+$font-weight-normal: $default-font-weight-normal;
+$font-weight-semibold: $default-font-weight-semibold;
+$font-size: $default-font-size;
+$font-size-small: $default-font-size-small;
+$font-size-medium: $default-font-size-medium;
+$h5-size: $default-h5-size;
+
+/*
+ * SPACING
+ */
+$base-spacing-unit: $default-base-spacing-unit;
+$half-spacing-unit: $default-half-spacing-unit;
+$quarter-spacing-unit: $default-quarter-spacing-unit;
+$sixth-spacing-unit: $default-sixth-spacing-unit;
+$eighth-spacing-unit: $default-eighth-spacing-unit;
+
+/*
+ * BORDERS
+ */
+$border-radius: $default-border-radius;
+$border-radius-5: $default-border-radius-5;
+
+/*
+ * COLORS
+ */
+
+// base
+$color-brand: $default-color-navyblue;
+$color-white: $default-color-white;
+$color-highlight-darker: $default-color-gray-blue-pale;
+
+// text
+$color-active-text: $default-color-rhino-core;
+$color-text: $default-color-rhino-dark;
+$color-secondary-text: $default-color-rhino-medium-1;
+$color-header-text: $default-color-rhino-core;
+$color-link-light: $default-color-blue-pale;
+
+// backgrounds
+$color-panel-background: $default-color-white;
+$color-section-header-background: $default-color-white-blue;
+
+// themes
+$color-border: $default-color-rhino-medium-2;
+$color-border-secondary: $default-color-rhino-light-1;
+$color-button-secondary: $default-color-rhino-light-2;
+$color-page-background: $default-color-rhino-light-3;