devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
480 lines (446 loc) • 17.6 kB
JavaScript
/**
* DevExtreme (viz/components/data_validator.js)
* Version: 18.1.3
* Build date: Tue May 15 2018
*
* Copyright (c) 2012 - 2018 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
;
var typeUtils = require("../../core/utils/type"),
STRING = "string",
NUMERIC = "numeric",
DATETIME = "datetime",
DISCRETE = "discrete",
SEMIDISCRETE = "semidiscrete",
CONTINUOUS = "continuous",
LOGARITHMIC = "logarithmic",
VALUE_TYPE = "valueType",
ARGUMENT_TYPE = "argumentType",
extend = require("../../core/utils/extend").extend,
axisTypeParser = require("../core/utils").enumParser([STRING, NUMERIC, DATETIME]),
_getParser = require("./parse_utils").getParser,
_isDefined = typeUtils.isDefined,
_isFunction = typeUtils.isFunction,
_isArray = Array.isArray,
_isString = typeUtils.isString,
_isDate = typeUtils.isDate,
_isNumber = typeUtils.isNumeric,
_isObject = typeUtils.isObject;
function groupingValues(data, others, valueField, index) {
if (index >= 0) {
data.slice(index).forEach(function(cell) {
if (_isDefined(cell[valueField])) {
others[valueField] += cell[valueField];
cell[valueField] = void 0
}
})
}
}
function processGroups(groups) {
groups.forEach(function(group) {
group.valueType = group.valueAxisType = null;
group.series.forEach(function(series) {
series.updateDataType({})
});
group.valueAxis && group.valueAxis.resetTypes(VALUE_TYPE)
})
}
function sortValues(data, asc, selector) {
var func = asc ? function(a, b) {
return a - b
} : function(a, b) {
return b - a
};
data.sort(function(a, b) {
var valA = selector(a),
valB = selector(b),
aa = _isDefined(valA) ? 1 : 0,
bb = _isDefined(valB) ? 1 : 0;
return aa && bb ? func(valA, valB) : func(aa, bb)
});
return data
}
function resetArgumentAxes(axes) {
axes && axes.forEach(function(axis) {
axis.resetTypes(ARGUMENT_TYPE)
})
}
function parseCategories(categories, parser) {
var newArray = [];
categories.forEach(function(category) {
var parsedCategory = parser(category);
void 0 !== parsedCategory && newArray.push(parsedCategory)
});
return newArray
}
function parseAxisCategories(groupsData, parsers) {
var argumentCategories = groupsData.argumentOptions && groupsData.argumentOptions.categories,
valueParser = parsers[1];
groupsData.groups.forEach(function(valueGroup) {
var categories = valueGroup.valueOptions && valueGroup.valueOptions.categories;
if (categories) {
valueGroup.valueOptions.categories = parseCategories(categories, valueParser)
}
});
if (argumentCategories) {
groupsData.argumentOptions.categories = parseCategories(argumentCategories, parsers[0])
}
}
function filterForLogAxis(val, field, incidentOccurred) {
if (val <= 0 && null !== val) {
incidentOccurred("E2004", [field]);
val = null
}
return val
}
function eigen(x) {
return x
}
function getType(unit, type) {
var result = type;
if (type === STRING || _isString(unit)) {
result = STRING
} else {
if (type === DATETIME || _isDate(unit)) {
result = DATETIME
} else {
if (_isNumber(unit)) {
result = NUMERIC
}
}
}
return result
}
function correctAxisType(type, axisType, hasCategories, incidentOccurred) {
if (type === STRING && (axisType === CONTINUOUS || axisType === LOGARITHMIC || axisType === SEMIDISCRETE)) {
incidentOccurred("E2002")
}
return axisType === LOGARITHMIC ? LOGARITHMIC : hasCategories || axisType === DISCRETE || type === STRING ? DISCRETE : axisType === SEMIDISCRETE ? SEMIDISCRETE : CONTINUOUS
}
function validUnit(unit, field, incidentOccurred) {
if (unit) {
incidentOccurred(!_isNumber(unit) && !_isDate(unit) && !_isString(unit) ? "E2003" : "E2004", [field])
}
}
function createParserUnit(type, axisType, ignoreEmptyPoints, skipFields, incidentOccurred) {
var parser = type ? _getParser(type) : eigen,
filter = axisType === LOGARITHMIC ? filterForLogAxis : eigen,
filterInfinity = axisType !== DISCRETE ? function(x) {
return isFinite(x) || void 0 === x ? x : null
} : eigen,
filterNulls = ignoreEmptyPoints ? function(x) {
return null === x ? void 0 : x
} : eigen;
return function(unit, field) {
var filterLogValues = function(x) {
return filter(x, field, incidentOccurred)
},
parseUnit = filterNulls(filterLogValues(filterInfinity(parser(unit))));
if (void 0 === parseUnit) {
skipFields[field] = (skipFields[field] || 0) + 1;
validUnit(unit, field, incidentOccurred)
}
return parseUnit
}
}
function prepareParsers(groupsData, skipFields, incidentOccurred) {
var sizeParser, valueParser, ignoreEmptyPoints, argumentParser = createParserUnit(groupsData.argumentType, groupsData.argumentAxisType, false, skipFields, incidentOccurred),
categoryParsers = [argumentParser],
cache = {},
list = [];
groupsData.groups.forEach(function(group) {
group.series.forEach(function(series) {
ignoreEmptyPoints = series.getOptions().ignoreEmptyPoints;
valueParser = createParserUnit(group.valueType, group.valueAxisType, ignoreEmptyPoints, skipFields, incidentOccurred);
sizeParser = createParserUnit(NUMERIC, CONTINUOUS, ignoreEmptyPoints, skipFields, incidentOccurred);
cache[series.getArgumentField()] = argumentParser;
series.getValueFields().forEach(function(field) {
!categoryParsers[1] && (categoryParsers[1] = valueParser);
cache[field] = valueParser
});
if (series.getSizeField()) {
cache[series.getSizeField()] = sizeParser
}
})
});
for (var field in cache) {
list.push([field, cache[field]])
}
list.length && parseAxisCategories(groupsData, categoryParsers);
return list
}
function getParsedCell(cell, parsers) {
var i, field, value, ii = parsers.length,
obj = extend({}, cell);
for (i = 0; i < ii; ++i) {
field = parsers[i][0];
value = cell[field];
obj[field] = parsers[i][1](value, field)
}
return obj
}
function parse(data, parsers) {
var i, parsedData = [],
ii = data.length;
parsedData.length = ii;
for (i = 0; i < ii; ++i) {
parsedData[i] = getParsedCell(data[i], parsers)
}
return parsedData
}
function findIndexByThreshold(data, valueField, threshold) {
var i, value, ii = data.length;
for (i = 0; i < ii; ++i) {
value = data[i][valueField];
if (_isDefined(value) && threshold > value) {
break
}
}
return i
}
function groupMinSlices(originalData, argumentField, valueField, smallValuesGrouping) {
smallValuesGrouping = smallValuesGrouping || {};
var data, mode = smallValuesGrouping.mode,
others = {};
if (!mode || "none" === mode) {
return
}
others[argumentField] = String(smallValuesGrouping.groupName || "others");
others[valueField] = 0;
data = sortValues(originalData.slice(), false, function(a) {
return a[valueField]
});
groupingValues(data, others, valueField, "smallValueThreshold" === mode ? findIndexByThreshold(data, valueField, smallValuesGrouping.threshold) : smallValuesGrouping.topCount);
others[valueField] && originalData.push(others)
}
function groupPieData(data, groupsData) {
var firstSeries = groupsData.groups[0] && groupsData.groups[0].series[0],
isPie = firstSeries && ("pie" === firstSeries.type || "doughnut" === firstSeries.type || "donut" === firstSeries.type);
if (!isPie) {
return
}
groupsData.groups.forEach(function(group) {
group.series.forEach(function(series) {
groupMinSlices(data, series.getArgumentField(), series.getValueFields()[0], series.getOptions().smallValuesGrouping)
})
})
}
function addUniqueItemToCollection(item, collection, itemsHash) {
if (!itemsHash[item]) {
collection.push(item);
itemsHash[item] = true
}
}
function getUniqueArgumentFields(groupsData) {
var uniqueArgumentFields = [],
hash = {};
groupsData.groups.forEach(function(group) {
group.series.forEach(function(series) {
addUniqueItemToCollection(series.getArgumentField(), uniqueArgumentFields, hash)
})
});
return uniqueArgumentFields
}
function sort(a, b) {
var result = a - b;
if (isNaN(result)) {
if (!_isDefined(a)) {
return 1
}
if (!_isDefined(b)) {
return -1
}
return 0
}
return result
}
function sortByArgument(data, argumentField) {
return data.slice().sort(function(a, b) {
return sort(a[argumentField], b[argumentField])
})
}
function sortByCallback(data, callback) {
return data.slice().sort(callback)
}
function checkValueTypeOfGroup(group, cell) {
group.series.forEach(function(series) {
series.getValueFields().forEach(function(field) {
group.valueType = getType(cell[field], group.valueType)
})
});
return group.valueType
}
function getSortByCategories(categories) {
var hash = {};
categories.forEach(function(value, i) {
hash[value] = i
});
return function(data, argumentField) {
return sortValues(data.slice(), true, function(a) {
return hash[a[argumentField]]
})
}
}
function sortData(data, groupsData, options, uniqueArgumentFields) {
var reSortCategories, dataByArguments = {},
isDiscrete = groupsData.argumentAxisType === DISCRETE,
userCategories = isDiscrete && groupsData.argumentOptions && groupsData.argumentOptions.categories,
sortFunction = function(data) {
return data
},
sortingMethodOption = options.sortingMethod;
if (!userCategories && _isFunction(sortingMethodOption)) {
data = sortByCallback(data, sortingMethodOption)
}
if (isDiscrete) {
groupsData.categories = getCategories(data, uniqueArgumentFields, userCategories)
}
if (userCategories || !_isFunction(sortingMethodOption) && groupsData.argumentType === STRING && !options._skipArgumentSorting) {
sortFunction = getSortByCategories(groupsData.categories)
} else {
if (true === sortingMethodOption && groupsData.argumentType !== STRING) {
sortFunction = sortByArgument;
reSortCategories = isDiscrete
}
}
uniqueArgumentFields.forEach(function(field) {
dataByArguments[field] = sortFunction(data, field)
});
if (reSortCategories) {
groupsData.categories = groupsData.categories.sort(sort)
}
return dataByArguments
}
function checkItemExistence(collection, item) {
return collection.map(function(collectionItem) {
return collectionItem.valueOf()
}).indexOf(item.valueOf()) === -1
}
function getCategories(data, uniqueArgumentFields, userCategories) {
var categories = userCategories ? userCategories.slice() : [];
uniqueArgumentFields.forEach(function(field) {
data.forEach(function(item) {
var dataItem = item[field];
_isDefined(dataItem) && checkItemExistence(categories, dataItem) && categories.push(dataItem)
})
});
return categories
}
function checkArgumentTypeOfGroup(series, cell, groupsData) {
series.forEach(function(currentSeries) {
groupsData.argumentType = getType(cell[currentSeries.getArgumentField()], groupsData.argumentType)
});
return groupsData.argumentType
}
function checkType(data, groupsData, checkTypeForAllData) {
var groupsIndexes, groupsWithUndefinedValueType = [],
groupsWithUndefinedArgumentType = [],
argumentTypeGroup = groupsData.argumentOptions && axisTypeParser(groupsData.argumentOptions.argumentType);
groupsData.groups.forEach(function(group) {
if (!group.series.length) {
return
}
var valueTypeGroup = group.valueOptions && axisTypeParser(group.valueOptions.valueType);
group.valueType = valueTypeGroup;
groupsData.argumentType = argumentTypeGroup;
!valueTypeGroup && groupsWithUndefinedValueType.push(group);
!argumentTypeGroup && groupsWithUndefinedArgumentType.push(group)
});
if (groupsWithUndefinedValueType.length || groupsWithUndefinedArgumentType.length) {
groupsIndexes = groupsWithUndefinedValueType.map(function(_, index) {
return index
});
data.some(function(cell) {
var defineArg;
groupsWithUndefinedValueType.forEach(function(group, groupIndex) {
if (checkValueTypeOfGroup(group, cell) && groupsIndexes.indexOf(groupIndex) >= 0) {
groupsIndexes.splice(groupIndex, 1)
}
});
if (!defineArg) {
groupsWithUndefinedArgumentType.forEach(function(group) {
defineArg = checkArgumentTypeOfGroup(group.series, cell, groupsData)
})
}
if (!checkTypeForAllData && defineArg && 0 === groupsIndexes.length) {
return true
}
})
}
}
function checkAxisType(groupsData, incidentOccurred) {
var argumentOptions = groupsData.argumentOptions || {},
userArgumentCategories = argumentOptions && argumentOptions.categories || [],
argumentAxisType = correctAxisType(groupsData.argumentType, argumentOptions.type, !!userArgumentCategories.length, incidentOccurred);
groupsData.groups.forEach(function(group) {
var valueOptions = group.valueOptions || {},
valueCategories = valueOptions.categories || [],
valueAxisType = correctAxisType(group.valueType, valueOptions.type, !!valueCategories.length, incidentOccurred);
group.series.forEach(function(series) {
var optionsSeries = {};
optionsSeries.argumentAxisType = argumentAxisType;
optionsSeries.valueAxisType = valueAxisType;
groupsData.argumentAxisType = groupsData.argumentAxisType || optionsSeries.argumentAxisType;
group.valueAxisType = group.valueAxisType || optionsSeries.valueAxisType;
optionsSeries.argumentType = groupsData.argumentType;
optionsSeries.valueType = group.valueType;
optionsSeries.showZero = valueOptions.showZero;
series.updateDataType(optionsSeries)
});
group.valueAxisType = group.valueAxisType || valueAxisType;
if (group.valueAxis) {
group.valueAxis.setTypes(group.valueAxisType, group.valueType, VALUE_TYPE);
group.valueAxis.validate()
}
});
groupsData.argumentAxisType = groupsData.argumentAxisType || argumentAxisType;
if (groupsData.argumentAxes) {
groupsData.argumentAxes.forEach(function(axis) {
axis.setTypes(groupsData.argumentAxisType, groupsData.argumentType, ARGUMENT_TYPE);
axis.validate()
})
}
}
function verifyData(source, incidentOccurred) {
var i, ii, k, item, data = [],
sourceIsDefined = _isDefined(source),
hasError = sourceIsDefined && !_isArray(source);
if (sourceIsDefined && !hasError) {
for (i = 0, ii = source.length, k = 0; i < ii; ++i) {
item = source[i];
if (_isObject(item)) {
data[k++] = item
} else {
if (item) {
hasError = true
}
}
}
}
if (hasError) {
incidentOccurred("E2001")
}
return data
}
function validateData(data, groupsData, incidentOccurred, options) {
var dataByArgumentFields, field, skipFields = {};
data = verifyData(data, incidentOccurred);
groupsData.argumentType = groupsData.argumentAxisType = null;
processGroups(groupsData.groups);
resetArgumentAxes(groupsData.argumentAxes);
checkType(data, groupsData, options.checkTypeForAllData);
checkAxisType(groupsData, incidentOccurred);
if (options.convertToAxisDataType) {
data = parse(data, prepareParsers(groupsData, skipFields, incidentOccurred))
}
groupPieData(data, groupsData);
dataByArgumentFields = sortData(data, groupsData, options, getUniqueArgumentFields(groupsData));
for (field in skipFields) {
if (skipFields[field] === data.length) {
incidentOccurred("W2002", [field])
}
}
return dataByArgumentFields
}
exports.validateData = validateData;