Skip to content

Commit 580907f

Browse files
committed
add customConfig visibility_rules
1 parent dbcee7d commit 580907f

File tree

8 files changed

+154
-13
lines changed

8 files changed

+154
-13
lines changed

dev/App.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import 'brace/theme/textmate';
1414
// https://github.com/plotly/react-chart-editor#mapbox-access-tokens
1515
import ACCESS_TOKENS from '../accessTokens';
1616

17+
import {customConfigTest} from '../src/__stories__';
18+
1719
const dataSourceOptions = Object.keys(dataSources).map(name => ({
1820
value: name,
1921
label: name,
@@ -183,6 +185,7 @@ class App extends Component {
183185
// makeDefaultTrace={() => ({type: 'scattergl', mode: 'markers'})}
184186
// fontOptions={[{label:'Arial', value: 'arial'}]}
185187
// chartHelp={chartHelp}
188+
// customConfig={customConfigTest}
186189
>
187190
<DefaultEditor
188191
// menuPanelOrder={[

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "react-chart-editor",
33
"description": "plotly.js chart editor react component UI",
4-
"version": "0.38.1",
4+
"version": "0.39.0",
55
"author": "Plotly, Inc.",
66
"bugs": {
77
"url": "https://github.com/plotly/react-chart-editor/issues"

src/EditorControls.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import DefaultEditor from './DefaultEditor';
22
import PropTypes from 'prop-types';
33
import React, {Component} from 'react';
4-
import {bem, localizeString, plotlyTraceToCustomTrace, traceTypeToPlotlyInitFigure} from './lib';
4+
import {
5+
bem,
6+
localizeString,
7+
plotlyTraceToCustomTrace,
8+
traceTypeToPlotlyInitFigure,
9+
hasValidCustomConfigVisibilityRules,
10+
} from './lib';
511
import {
612
shamefullyClearAxisTypes,
713
shamefullyAdjustAxisRef,
@@ -59,6 +65,10 @@ class EditorControls extends Component {
5965
mapBoxAccess: this.props.mapBoxAccess,
6066
fontOptions: this.props.fontOptions,
6167
chartHelp: this.props.chartHelp,
68+
customConfig: this.props.customConfig,
69+
hasValidCustomConfigVisibilityRules: hasValidCustomConfigVisibilityRules(
70+
this.props.customConfig
71+
),
6272
};
6373
}
6474

@@ -391,6 +401,7 @@ EditorControls.propTypes = {
391401
mapBoxAccess: PropTypes.bool,
392402
fontOptions: PropTypes.array,
393403
chartHelp: PropTypes.object,
404+
customConfig: PropTypes.object,
394405
};
395406

396407
EditorControls.defaultProps = {
@@ -432,6 +443,8 @@ EditorControls.childContextTypes = {
432443
mapBoxAccess: PropTypes.bool,
433444
fontOptions: PropTypes.array,
434445
chartHelp: PropTypes.object,
446+
customConfig: PropTypes.object,
447+
hasValidCustomConfigVisibilityRules: PropTypes.bool,
435448
};
436449

437450
export default EditorControls;

src/PlotlyEditor.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class PlotlyEditor extends Component {
4040
mapBoxAccess={Boolean(this.props.config && this.props.config.mapboxAccessToken)}
4141
fontOptions={this.props.fontOptions}
4242
chartHelp={this.props.chartHelp}
43+
customConfig={this.props.customConfig}
4344
>
4445
{this.props.children}
4546
</EditorControls>
@@ -91,6 +92,7 @@ PlotlyEditor.propTypes = {
9192
glByDefault: PropTypes.bool,
9293
fontOptions: PropTypes.array,
9394
chartHelp: PropTypes.object,
95+
customConfig: PropTypes.object,
9496
};
9597

9698
PlotlyEditor.defaultProps = {

src/components/containers/PlotlySection.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import React, {Component} from 'react';
22
import PropTypes from 'prop-types';
3-
import {containerConnectedContextTypes, unpackPlotProps} from '../../lib';
3+
import {
4+
containerConnectedContextTypes,
5+
unpackPlotProps,
6+
isVisibleGivenCustomConfig,
7+
} from '../../lib';
48

59
export class Section extends Component {
610
constructor() {
@@ -45,7 +49,7 @@ export default class PlotlySection extends Section {
4549

4650
determineVisibility(nextProps, nextContext) {
4751
const {isVisible} = unpackPlotProps(nextProps, nextContext);
48-
this.sectionVisible = Boolean(isVisible);
52+
this.sectionVisible = isVisibleGivenCustomConfig(isVisible, nextProps, nextContext);
4953

5054
React.Children.forEach(nextProps.children, child => {
5155
if (!child || this.sectionVisible) {
@@ -57,7 +61,13 @@ export default class PlotlySection extends Section {
5761
if (child.type.modifyPlotProps) {
5862
child.type.modifyPlotProps(child.props, nextContext, plotProps);
5963
}
60-
this.sectionVisible = this.sectionVisible || plotProps.isVisible;
64+
65+
this.sectionVisible = isVisibleGivenCustomConfig(
66+
this.sectionVisible || plotProps.isVisible,
67+
child.props,
68+
nextContext,
69+
child.type && child.type.displayName ? child.type.displayName : null
70+
);
6171
return;
6272
}
6373

src/lib/connectToContainer.js

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, {Component} from 'react';
22
import PropTypes from 'prop-types';
3-
import unpackPlotProps from './unpackPlotProps';
3+
import unpackPlotProps, {isVisibleGivenCustomConfig} from './unpackPlotProps';
44
import {getDisplayName} from '../lib';
55

66
export const containerConnectedContextTypes = {
@@ -18,6 +18,8 @@ export const containerConnectedContextTypes = {
1818
plotly: PropTypes.object,
1919
updateContainer: PropTypes.func,
2020
traceIndexes: PropTypes.array,
21+
customConfig: PropTypes.object,
22+
hasValidCustomConfigVisibilityRules: PropTypes.bool,
2123
};
2224

2325
export default function connectToContainer(WrappedComponent, config = {}) {
@@ -44,7 +46,7 @@ export default function connectToContainer(WrappedComponent, config = {}) {
4446
}
4547

4648
setLocals(props, context) {
47-
this.plotProps = unpackPlotProps(props, context);
49+
this.plotProps = unpackPlotProps(props, context, WrappedComponent);
4850
this.attr = props.attr;
4951
ContainerConnectedComponent.modifyPlotProps(props, context, this.plotProps);
5052
}
@@ -62,7 +64,17 @@ export default function connectToContainer(WrappedComponent, config = {}) {
6264
// is also wrapped by a component that `unpackPlotProps`. That way inner
6365
// component can skip computation as it can see plotProps is already defined.
6466
const {plotProps = this.plotProps, ...props} = Object.assign({}, this.plotProps, this.props);
65-
if (props.isVisible) {
67+
const wrappedComponentDisplayName =
68+
WrappedComponent && WrappedComponent.displayName ? WrappedComponent.displayName : null;
69+
70+
if (
71+
isVisibleGivenCustomConfig(
72+
props.isVisible,
73+
props,
74+
this.context,
75+
wrappedComponentDisplayName
76+
)
77+
) {
6678
return <WrappedComponent {...props} plotProps={plotProps} />;
6779
}
6880

src/lib/index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ import getAllAxes, {
2323
} from './getAllAxes';
2424
import localize, {localizeString} from './localize';
2525
import tinyColor from 'tinycolor2';
26-
import unpackPlotProps from './unpackPlotProps';
26+
import unpackPlotProps, {
27+
computeCustomConfigVisibility,
28+
hasValidCustomConfigVisibilityRules,
29+
isVisibleGivenCustomConfig,
30+
} from './unpackPlotProps';
2731
import walkObject, {isPlainObject} from './walkObject';
2832
import {traceTypeToPlotlyInitFigure, plotlyTraceToCustomTrace} from './customTraceType';
2933
import * as PlotlyIcons from 'plotly-icons';
@@ -216,6 +220,7 @@ function getParsedTemplateString(originalString, context) {
216220

217221
export {
218222
adjustColorscale,
223+
computeCustomConfigVisibility,
219224
axisIdToAxisName,
220225
bem,
221226
camelCase,
@@ -244,6 +249,7 @@ export {
244249
getFullTrace,
245250
getSubplotTitle,
246251
isPlainObject,
252+
hasValidCustomConfigVisibilityRules,
247253
localize,
248254
localizeString,
249255
lowerCase,
@@ -261,5 +267,6 @@ export {
261267
transpose,
262268
unpackPlotProps,
263269
upperCase,
270+
isVisibleGivenCustomConfig,
264271
walkObject,
265272
};

src/lib/unpackPlotProps.js

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,104 @@
1+
/* eslint-disable no-console */
2+
13
import nestedProperty from 'plotly.js/src/lib/nested_property';
24
import isNumeric from 'fast-isnumeric';
35
import {MULTI_VALUED, MULTI_VALUED_PLACEHOLDER} from './constants';
46

7+
const hasFullValue = fullValue => fullValue !== void 0 && fullValue !== null;
8+
9+
export function hasValidCustomConfigVisibilityRules(customConfig) {
10+
if (
11+
customConfig &&
12+
customConfig === Object(customConfig) &&
13+
Object.keys(customConfig).length &&
14+
customConfig.visibility_rules
15+
) {
16+
if (customConfig.visibility_rules.blacklist && customConfig.visibility_rules.whitelist) {
17+
console.error(
18+
'customConfig.visibility_rules can have a blacklist OR whitelist key, both are present in your config.'
19+
);
20+
return false;
21+
}
22+
23+
if (
24+
!Object.keys(customConfig.visibility_rules).some(key =>
25+
['blacklist', 'whitelist'].includes(key)
26+
)
27+
) {
28+
console.error(
29+
'customConfig.visibility_rules must have at least a blacklist or whitelist key.'
30+
);
31+
return false;
32+
}
33+
34+
const isValidRule = rule => {
35+
if (rule.exceptions) {
36+
return rule.exceptions.every(isValidRule);
37+
}
38+
return rule.type && ['attrName', 'controlType'].includes(rule.type) && rule.regex_match;
39+
};
40+
41+
const errorMessage =
42+
"All rules and exceptions must have a type (one of: 'attrName' or 'controlType') and regex_match key.";
43+
44+
if (
45+
customConfig.visibility_rules.blacklist &&
46+
!customConfig.visibility_rules.blacklist.every(isValidRule)
47+
) {
48+
console.error(errorMessage);
49+
return false;
50+
}
51+
52+
if (
53+
customConfig.visibility_rules.whitelist &&
54+
!customConfig.visibility_rules.whitelist.every(isValidRule)
55+
) {
56+
console.error(errorMessage);
57+
return false;
58+
}
59+
60+
return true;
61+
}
62+
return false;
63+
}
64+
65+
export function computeCustomConfigVisibility(props, customConfig, wrappedComponentDisplayName) {
66+
let isVisible;
67+
68+
const isRegexMatch = rule => {
69+
const stringToTest = rule.type === 'attrName' ? props.attr : wrappedComponentDisplayName;
70+
return RegExp(rule.regex_match).test(stringToTest);
71+
};
72+
73+
const passesTest = rule => {
74+
const hasException = rule => {
75+
if (rule.exceptions) {
76+
return rule.exceptions.some(exception => passesTest(exception));
77+
}
78+
return false;
79+
};
80+
return isRegexMatch(rule) && !hasException(rule);
81+
};
82+
83+
if (customConfig.visibility_rules.blacklist) {
84+
isVisible = !customConfig.visibility_rules.blacklist.some(passesTest);
85+
}
86+
87+
if (customConfig.visibility_rules.whitelist) {
88+
isVisible = customConfig.visibility_rules.whitelist.some(passesTest);
89+
}
90+
91+
return isVisible;
92+
}
93+
94+
export function isVisibleGivenCustomConfig(initial, nextProps, nextContext, componentDisplayName) {
95+
let show = initial;
96+
if (show && nextContext.hasValidCustomConfigVisibilityRules) {
97+
show = computeCustomConfigVisibility(nextProps, nextContext.customConfig, componentDisplayName);
98+
}
99+
return show;
100+
}
101+
5102
export default function unpackPlotProps(props, context) {
6103
const {container, getValObject, defaultContainer, updateContainer} = context;
7104

@@ -28,10 +125,7 @@ export default function unpackPlotProps(props, context) {
28125
multiValued = true;
29126
}
30127

31-
let isVisible = false;
32-
if (props.show || (fullValue !== void 0 && fullValue !== null)) {
33-
isVisible = true;
34-
}
128+
const isVisible = Boolean(hasFullValue(fullValue) || props.show);
35129

36130
let defaultValue = props.defaultValue;
37131
if (defaultValue === void 0 && defaultContainer) {

0 commit comments

Comments
 (0)