Skip to content

Introducing Plotly.validate(data, layout) #697

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Jul 13, 2016
Merged
32 changes: 17 additions & 15 deletions src/components/colorscale/is_valid_scale_array.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,23 @@ var tinycolor = require('tinycolor2');


module.exports = function isValidScaleArray(scl) {
var isValid = true,
highestVal = 0,
si;

if(!Array.isArray(scl)) return false;
else {
if(+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false;
for(var i = 0; i < scl.length; i++) {
si = scl[i];
if(si.length !== 2 || +si[0] < highestVal || !tinycolor(si[1]).isValid()) {
isValid = false;
break;
}
highestVal = +si[0];
var highestVal = 0;

if(!Array.isArray(scl) || scl.length < 2) return false;

if(!scl[0] || !scl[scl.length - 1]) return false;

if(+scl[0][0] !== 0 || +scl[scl.length - 1][0] !== 1) return false;

for(var i = 0; i < scl.length; i++) {
var si = scl[i];

if(si.length !== 2 || +si[0] < highestVal || !tinycolor(si[1]).isValid()) {
return false;
}
return isValid;

highestVal = +si[0];
}

return true;
};
1 change: 1 addition & 0 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ exports.setPlotConfig = require('./plot_api/set_plot_config');
exports.register = Plotly.register;
exports.toImage = require('./plot_api/to_image');
exports.downloadImage = require('./snapshot/download');
exports.validate = require('./plot_api/validate');

// plot icons
exports.Icons = require('../build/ploticon');
Expand Down
67 changes: 56 additions & 11 deletions src/lib/coerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,14 @@ exports.valObjects = {
// TODO 'values shouldn't be in there (edge case: 'dash' in Scatter)
otherOpts: ['dflt', 'noBlank', 'strict', 'arrayOk', 'values'],
coerceFunction: function(v, propOut, dflt, opts) {
if(opts.strict === true && typeof v !== 'string') {
propOut.set(dflt);
return;
}
if(typeof v !== 'string') {
var okToCoerce = (typeof v === 'number');

var s = String(v);
if(v === undefined || (opts.noBlank === true && !s)) {
propOut.set(dflt);
if(opts.strict === true || !okToCoerce) propOut.set(dflt);
else propOut.set(String(v));
}
else propOut.set(s);
else if(opts.noBlank && !v) propOut.set(dflt);
else propOut.set(v);
}
},
color: {
Expand Down Expand Up @@ -162,11 +160,11 @@ exports.valObjects = {
subplotid: {
description: [
'An id string of a subplot type (given by dflt), optionally',
'followed by an integer >1. e.g. if dflt=\'geo\', we can have',
'followed by an integer >1. e.g. if dflt=\'geo\', we can have',
'\'geo\', \'geo2\', \'geo3\', ...'
].join(' '),
requiredOpts: [],
otherOpts: ['dflt'],
requiredOpts: ['dflt'],
otherOpts: [],
coerceFunction: function(v, propOut, dflt) {
var dlen = dflt.length;
if(typeof v === 'string' && v.substr(0, dlen) === dflt &&
Expand All @@ -175,6 +173,18 @@ exports.valObjects = {
return;
}
propOut.set(dflt);
},
validateFunction: function(v, opts) {
var dflt = opts.dflt,
dlen = dflt.length;

if(v === dflt) return true;
if(typeof v !== 'string') return false;
if(v.substr(0, dlen) === dflt && idRegex.test(v.substr(dlen))) {
return true;
}

return false;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@alexcjohnson I chose to write a validateFunction for the 'subplotid' val type (only 'subplotid' and 'info_array' now have validateFunction).

This felt like (1) less of a hassle than adding a base option and (2) less hacky than trying to patch into Lib.validate. I hope you're ok with ⏫ .

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, you made the right call. As long as there are good tests, which it looks like you added 🎉

}
},
flaglist: {
Expand Down Expand Up @@ -239,6 +249,22 @@ exports.valObjects = {
}

propOut.set(vOut);
},
validateFunction: function(v, opts) {
if(!Array.isArray(v)) return false;

var items = opts.items;

if(v.length !== items.length) return false;

// valid when all items are valid
for(var i = 0; i < items.length; i++) {
var isItemValid = exports.validate(v[i], opts.items[i]);

if(!isItemValid) return false;
}

return true;
}
}
};
Expand Down Expand Up @@ -309,3 +335,22 @@ exports.coerceFont = function(coerce, attr, dfltObj) {

return out;
};

exports.validate = function(value, opts) {
var valObject = exports.valObjects[opts.valType];

if(opts.arrayOk && Array.isArray(value)) return true;

if(valObject.validateFunction) {
return valObject.validateFunction(value, opts);
}

var failed = {},
out = failed,
propMock = { set: function(v) { out = v; } };

// 'failed' just something mutable that won't be === anything else

valObject.coerceFunction(value, propMock, failed, opts);
Copy link
Member

@bpostlethwaite bpostlethwaite Jul 13, 2016

Choose a reason for hiding this comment

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

so the

failed = {}
out = failed
coerce(() => out = v)
out !== failed

dance is just checking to see if if propMock is fired by checking to see if the exising out === failed reference is no longer valid?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Exactly.

Copy link
Member

Choose a reason for hiding this comment

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

would something like

var called = false;
var propMock = {set: function() { called = true }

 valObject.coerceFunction(value, propMock, failed, opts);

return called;

also work?

Copy link
Contributor Author

@etpinard etpinard Jul 13, 2016

Choose a reason for hiding this comment

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

No. Because we want coerced values to be considered valid.

For example,

Lib.validate('1', { valType: 'number' });  // => true

even though 1 will be inserted in fullData.

In other words, out and failed will be linked to different refs when the coerce function makes propMock execute propMock.set(dflt).

Copy link
Member

Choose a reason for hiding this comment

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

right makes sense

return out !== failed;
};
1 change: 1 addition & 0 deletions src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ lib.valObjects = coerceModule.valObjects;
lib.coerce = coerceModule.coerce;
lib.coerce2 = coerceModule.coerce2;
lib.coerceFont = coerceModule.coerceFont;
lib.validate = coerceModule.validate;

var datesModule = require('./dates');
lib.dateTime2ms = datesModule.dateTime2ms;
Expand Down
Loading