devextreme
Version:
HTML5 JavaScript Component Suite for Responsive Web Development
467 lines (435 loc) • 16.7 kB
JavaScript
/**
* DevExtreme (esm/viz/components/data_validator.js)
* Version: 24.2.6
* Build date: Mon Mar 17 2025
*
* Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED
* Read about DevExtreme licensing here: https://js.devexpress.com/Licensing/
*/
import {
isDefined as _isDefined,
isFunction as _isFunction,
isString as _isString,
isDate as _isDate,
isNumeric as _isNumber,
isObject as _isObject
} from "../../core/utils/type";
const STRING = "string";
const NUMERIC = "numeric";
const DATETIME = "datetime";
const DISCRETE = "discrete";
const SEMIDISCRETE = "semidiscrete";
const CONTINUOUS = "continuous";
const LOGARITHMIC = "logarithmic";
const VALUE_TYPE = "valueType";
const ARGUMENT_TYPE = "argumentType";
import {
extend
} from "../../core/utils/extend";
import {
enumParser
} from "../core/utils";
const axisTypeParser = enumParser([STRING, NUMERIC, DATETIME]);
import {
getParser as _getParser
} from "./parse_utils";
const _isArray = Array.isArray;
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) {
const func = asc ? function(a, b) {
return a - b
} : function(a, b) {
return b - a
};
data.sort((function(a, b) {
const valA = selector(a);
const valB = selector(b);
const aa = _isDefined(valA) ? 1 : 0;
const 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) {
const newArray = [];
categories.forEach((function(category) {
const parsedCategory = parser(category);
void 0 !== parsedCategory && newArray.push(parsedCategory)
}));
return newArray
}
function parseAxisCategories(groupsData, parsers) {
const argumentCategories = groupsData.argumentOptions && groupsData.argumentOptions.categories;
groupsData.groups.forEach((function(valueGroup, i) {
const categories = valueGroup.valueOptions && valueGroup.valueOptions.categories;
if (categories) {
valueGroup.valueOptions.categories = parseCategories(categories, parsers[i + 1])
}
}));
if (argumentCategories) {
groupsData.argumentOptions.categories = parseCategories(argumentCategories, parsers[0])
}
}
function eigen(x) {
return x
}
function getType(unit, type) {
let 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, incidentOccurred) {
const parser = type ? _getParser(type) : eigen;
const filterInfinity = axisType !== DISCRETE ? function(x) {
return isFinite(x) || void 0 === x ? x : null
} : eigen;
return function(unit, field) {
const parseUnit = filterInfinity(parser(unit));
if (void 0 === parseUnit) {
validUnit(unit, field, incidentOccurred)
}
return parseUnit
}
}
function prepareParsers(groupsData, incidentOccurred) {
const argumentParser = createParserUnit(groupsData.argumentType, groupsData.argumentAxisType, incidentOccurred);
let sizeParser;
let valueParser;
const categoryParsers = [argumentParser];
const cache = {};
const list = [];
groupsData.groups.forEach((function(group, groupIndex) {
group.series.forEach((function(series) {
valueParser = createParserUnit(group.valueType, group.valueAxisType, incidentOccurred);
sizeParser = createParserUnit(NUMERIC, CONTINUOUS, incidentOccurred);
cache[series.getArgumentField()] = argumentParser;
series.getValueFields().forEach((function(field) {
categoryParsers[groupIndex + 1] = valueParser;
cache[field] = valueParser
}));
if (series.getSizeField()) {
cache[series.getSizeField()] = sizeParser
}
}))
}));
for (const field in cache) {
list.push([field, cache[field]])
}
list.length && parseAxisCategories(groupsData, categoryParsers);
return list
}
function getParsedCell(cell, parsers) {
let i;
const ii = parsers.length;
const obj = extend({}, cell);
let field;
let value;
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) {
const parsedData = [];
let i;
const 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) {
let i;
const ii = data.length;
let value;
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 || {};
const mode = smallValuesGrouping.mode;
const others = {};
if (!mode || "none" === mode) {
return
}
others[argumentField] = String(smallValuesGrouping.groupName || "others");
others[valueField] = 0;
const 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) {
const firstSeries = groupsData.groups[0] && groupsData.groups[0].series[0];
const 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) {
const uniqueArgumentFields = [];
const hash = {};
groupsData.groups.forEach((function(group) {
group.series.forEach((function(series) {
addUniqueItemToCollection(series.getArgumentField(), uniqueArgumentFields, hash)
}))
}));
return uniqueArgumentFields
}
function sort(a, b) {
const 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) {
const 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) {
const dataByArguments = {};
const isDiscrete = groupsData.argumentAxisType === DISCRETE;
const userCategories = isDiscrete && groupsData.argumentOptions && groupsData.argumentOptions.categories;
let sortFunction = function(data) {
return data
};
const sortingMethodOption = options.sortingMethod;
let reSortCategories;
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 -1 === collection.map((function(collectionItem) {
return collectionItem.valueOf()
})).indexOf(item.valueOf())
}
function getCategories(data, uniqueArgumentFields, userCategories) {
const categories = userCategories ? userCategories.slice() : [];
uniqueArgumentFields.forEach((function(field) {
data.forEach((function(item) {
const 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) {
const groupsWithUndefinedValueType = [];
const groupsWithUndefinedArgumentType = [];
const argumentTypeGroup = groupsData.argumentOptions && axisTypeParser(groupsData.argumentOptions.argumentType);
let groupsIndexes;
groupsData.groups.forEach((function(group) {
if (!group.series.length) {
return
}
const 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) {
let 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) {
const argumentOptions = groupsData.argumentOptions || {};
const userArgumentCategories = argumentOptions && argumentOptions.categories || [];
const argumentAxisType = correctAxisType(groupsData.argumentType, argumentOptions.type, !!userArgumentCategories.length, incidentOccurred);
groupsData.groups.forEach((function(group) {
const valueOptions = group.valueOptions || {};
const valueCategories = valueOptions.categories || [];
const valueAxisType = correctAxisType(group.valueType, valueOptions.type, !!valueCategories.length, incidentOccurred);
group.series.forEach((function(series) {
const 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) {
const data = [];
const sourceIsDefined = _isDefined(source);
let hasError = sourceIsDefined && !_isArray(source);
let i;
let ii;
let k;
let item;
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
}
export function validateData(data, groupsData, incidentOccurred, options) {
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, incidentOccurred))
}
groupPieData(data, groupsData);
const dataByArgumentFields = sortData(data, groupsData, options, getUniqueArgumentFields(groupsData));
return dataByArgumentFields
}