UNPKG

@progress/kendo-charts

Version:

Kendo UI platform-independent Charts library

128 lines (104 loc) 4.29 kB
import SeriesBinder from '../series-binder'; import { OBJECT } from '../../common/constants'; import { defined, getter, isArray, isNumber } from '../../common'; const STD_ERR = "stderr"; const STD_DEV = "stddev"; const percentRegex = /percent(?:\w*)\((\d+)\)/; const standardDeviationRegex = new RegExp("^" + STD_DEV + "(?:\\((\\d+(?:\\.\\d+)?)\\))?$"); class ErrorRangeCalculator { constructor(errorValue, series, field) { this.initGlobalRanges(errorValue, series, field); } initGlobalRanges(errorValue, series, field) { const data = series.data; const deviationMatch = standardDeviationRegex.exec(errorValue); if (deviationMatch) { this.valueGetter = this.createValueGetter(series, field); const average = this.getAverage(data); const deviation = this.getStandardDeviation(data, average, false); const multiple = deviationMatch[1] ? parseFloat(deviationMatch[1]) : 1; const errorRange = { low: average.value - deviation * multiple, high: average.value + deviation * multiple }; this.globalRange = function() { return errorRange; }; } else if (errorValue.indexOf && errorValue.indexOf(STD_ERR) >= 0) { this.valueGetter = this.createValueGetter(series, field); const standardError = this.getStandardError(data, this.getAverage(data)); this.globalRange = function(value) { return { low: value - standardError, high: value + standardError }; }; } } createValueGetter(series, field) { const data = series.data; const binder = SeriesBinder.current; const valueFields = binder.valueFields(series); const item = defined(data[0]) ? data[0] : {}; let valueGetter; if (isArray(item)) { const index = field ? valueFields.indexOf(field) : 0; valueGetter = getter("[" + index + "]"); } else if (isNumber(item)) { valueGetter = getter(); } else if (typeof item === OBJECT) { const srcValueFields = binder.sourceFields(series, valueFields); valueGetter = getter(srcValueFields[valueFields.indexOf(field)]); } return valueGetter; } getErrorRange(pointValue, errorValue) { let low, high, value; if (!defined(errorValue)) { return null; } if (this.globalRange) { return this.globalRange(pointValue); } if (isArray(errorValue)) { low = pointValue - errorValue[0]; high = pointValue + errorValue[1]; } else if (isNumber(value = parseFloat(errorValue))) { low = pointValue - value; high = pointValue + value; } else if ((value = percentRegex.exec(errorValue))) { const percentValue = pointValue * (parseFloat(value[1]) / 100); low = pointValue - Math.abs(percentValue); high = pointValue + Math.abs(percentValue); } else { throw new Error("Invalid ErrorBar value: " + errorValue); } return { low: low, high: high }; } getStandardError(data, average) { return this.getStandardDeviation(data, average, true) / Math.sqrt(average.count); } getStandardDeviation(data, average, isSample) { const length = data.length; const total = isSample ? average.count - 1 : average.count; let squareDifferenceSum = 0; for (let idx = 0; idx < length; idx++) { const value = this.valueGetter(data[idx]); if (isNumber(value)) { squareDifferenceSum += Math.pow(value - average.value, 2); } } return Math.sqrt(squareDifferenceSum / total); } getAverage(data) { const length = data.length; let sum = 0; let count = 0; for (let idx = 0; idx < length; idx++) { const value = this.valueGetter(data[idx]); if (isNumber(value)) { sum += value; count++; } } return { value: sum / count, count: count }; } } export default ErrorRangeCalculator;