UNPKG

react-mapfilter

Version:

These components are designed for viewing data in Mapeo. They share a common interface:

351 lines (313 loc) 9.97 kB
import "core-js/modules/es.array.iterator"; import "core-js/modules/web.dom-collections.iterator"; import _mapInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/map"; import _indexOfInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/index-of"; import _filterInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/filter"; import _valuesInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/values"; import _JSON$stringify from "@babel/runtime-corejs3/core-js-stable/json/stringify"; import _forEachInstanceProperty from "@babel/runtime-corejs3/core-js-stable/instance/for-each"; import _Map from "@babel/runtime-corejs3/core-js-stable/map"; // @flow import cloneDeep from 'clone-deep'; import { flatObjectEntries } from '../../utils/flat_object_entries'; import { guessValueType } from './value_types'; import * as valueTypes from '../../constants/value_types'; import { leftPad } from '../../utils/helpers'; /*:: import type { JSONObject, Statistics, FieldStatistic, StringStatistic, NumberStatistic, DateStatistic } from '../../types'*/ function defaultStats() /*: FieldStatistic*/ { return { [valueTypes.STRING]: { count: 0, values: new _Map() }, [valueTypes.NUMBER]: { count: 0, values: new _Map() }, [valueTypes.DATE]: { count: 0, values: new _Map() }, [valueTypes.DATETIME]: { count: 0, values: new _Map() }, [valueTypes.BOOLEAN]: { count: 0, values: new _Map([[true, 0], [false, 0]]) }, [valueTypes.URL]: 0, [valueTypes.IMAGE_URL]: 0, [valueTypes.AUDIO_URL]: 0, [valueTypes.VIDEO_URL]: 0, [valueTypes.NULL]: 0, [valueTypes.UNDEFINED]: 0, [valueTypes.LOCATION]: 0 }; } export default function createMemoizedStats() { let stats /*: Statistics*/ = {}; let dataMemo = []; return function getStats(data /*: Array<JSONObject>*/ ) /*: Statistics*/ { if (data === dataMemo) return stats; const { added, removed } = diffArrays(dataMemo, data); if (!added.length && !removed.length) return cloneDeep(stats); if (removed.length) { // Anything removed, calculate all the stats // TODO: more efficient stats on removal stats = {}; _forEachInstanceProperty(data).call(data, item => addItemStats(item, stats)); } else { // Only added items -> only need to process new items _forEachInstanceProperty(added).call(added, item => addItemStats(item, stats)); } dataMemo = data; return cloneDeep(stats); }; } function addItemStats(item /*: JSONObject*/ = {}, stats /*: Statistics*/ ) { var _context; _forEachInstanceProperty(_context = flatObjectEntries(item)).call(_context, function ([path, value]) { const key = _JSON$stringify(path); if (!stats[key]) stats[key] = defaultStats(); addFieldStats(value, stats[key]); }); } function addFieldStats(value /*: any*/ , fieldStats /*: FieldStatistic*/ ) { const type = guessValueType(value); if (typeof fieldStats[type] === 'number') { fieldStats[type] += 1; return; } switch (type) { case valueTypes.ARRAY: let arrayStats = fieldStats[valueTypes.ARRAY]; if (arrayStats === undefined) { arrayStats = fieldStats[valueTypes.ARRAY] = { count: 0, lengthMin: +Infinity, lengthMax: -Infinity, valueStats: defaultStats() }; } addArrayStats(value, arrayStats); return; case valueTypes.STRING: addStringStats(value, fieldStats[valueTypes.STRING]); return; case valueTypes.NUMBER: addNumberStats(value, fieldStats[valueTypes.NUMBER]); return; case valueTypes.DATE: addDateStats(value, fieldStats[valueTypes.DATE]); return; case valueTypes.DATETIME: addDateTimeStats(value, fieldStats[valueTypes.DATETIME]); return; case valueTypes.BOOLEAN: fieldStats[valueTypes.BOOLEAN].count += 1; _valuesInstanceProperty(fieldStats[valueTypes.BOOLEAN]).set(value, _valuesInstanceProperty(fieldStats[valueTypes.BOOLEAN]).get(value) + 1); return; default: // $FlowFixMe - flow doesn't realise that type is not values above fieldStats[type].count += 1; } } function addArrayStats(value /*: []*/ , stats /*: $NonMaybeType<$ElementType<FieldStatistic, 'array'>>*/ ) { if (value.length > stats.lengthMax) stats.lengthMax = value.length; if (value.length < stats.lengthMin) stats.lengthMin = value.length; stats.count += 1; _forEachInstanceProperty(value).call(value, function (item) { addFieldStats(item, stats.valueStats); }); } function addNumberStats(value /*: number*/ , stats /*: NumberStatistic*/ ) { stats.count += 1; const { min, max, variance, mean } = statReduce(stats, value, stats.count - 1); stats.min = min; stats.max = max; stats.variance = variance; stats.mean = mean; if (_valuesInstanceProperty(stats).has(value)) _valuesInstanceProperty(stats).set(value, _valuesInstanceProperty(stats).get(value) + 1);else _valuesInstanceProperty(stats).set(value, 1); } function addDateTimeStats(value /*: string*/ , stats /*: DateStatistic*/ ) { const dateAsNumber = +Date.parse(value); stats.count += 1; const { mean } = statReduce({ mean: stats.mean !== undefined ? +Date.parse(stats.mean) : undefined }, dateAsNumber, stats.count - 1); stats.min = stats.min === undefined ? value : value < stats.min ? value : stats.min; stats.max = stats.max === undefined ? value : value > stats.max ? value : stats.max; stats.mean = mean !== undefined ? new Date(mean).toISOString() : undefined; if (_valuesInstanceProperty(stats).has(value)) _valuesInstanceProperty(stats).set(value, _valuesInstanceProperty(stats).get(value) + 1);else _valuesInstanceProperty(stats).set(value, 1); } /** This requires slightly special treatment because date does not include time */ function addDateStats(value /*: string*/ , stats /*: DateStatistic*/ ) { const dateAsNumber = dateToNumber(value); stats.count += 1; const { mean } = statReduce({ mean: stats.mean !== undefined ? dateToNumber(stats.mean) : undefined }, dateAsNumber, stats.count - 1); stats.min = stats.min === undefined ? value : value < stats.min ? value : stats.min; stats.max = stats.max === undefined ? value : value > stats.max ? value : stats.max; stats.mean = mean !== undefined ? numberToDate(mean) : undefined; if (_valuesInstanceProperty(stats).has(value)) _valuesInstanceProperty(stats).set(value, _valuesInstanceProperty(stats).get(value) + 1);else _valuesInstanceProperty(stats).set(value, 1); } function addStringStats(value /*: string*/ , stats /*: StringStatistic*/ ) { stats.count += 1; const lengthStats = statReduce({ min: stats.lengthMin, max: stats.lengthMax, variance: stats.lengthVariance, mean: stats.lengthMean }, value.length, stats.count - 1); const wordStats = statReduce({ min: stats.wordsMin, max: stats.wordsMax, variance: stats.wordsVariance, mean: stats.wordsMean }, value.split(' ').length, stats.count - 1); stats.lengthMin = lengthStats.min; stats.lengthMax = lengthStats.max; stats.lengthVariance = lengthStats.variance; stats.lengthMean = lengthStats.mean; stats.wordsMin = wordStats.min; stats.wordsMax = wordStats.max; stats.wordsVariance = wordStats.variance; stats.wordsMean = wordStats.mean; if (_valuesInstanceProperty(stats).has(value)) _valuesInstanceProperty(stats).set(value, _valuesInstanceProperty(stats).get(value) + 1);else _valuesInstanceProperty(stats).set(value, 1); } /** * Compare two arrays of objects and return items in the second but not in the * first (added) and items in the first but not in the second (removed). * Compares using strict equality. */ export function diffArrays(oldArray /*: Array<Object>*/ , newArray /*: Array<Object>*/ ) /*: { added: Array<Object>, removed: Array<Object> }*/ { const added = _filterInstanceProperty(newArray).call(newArray, v => _indexOfInstanceProperty(oldArray).call(oldArray, v) === -1); const removed = _filterInstanceProperty(oldArray).call(oldArray, v => _indexOfInstanceProperty(newArray).call(newArray, v) === -1); return { added, removed }; } /*:: type MathStat = { mean?: number, variance?: number, min?: number, max?: number }*/ /** * Reducer that computes running mean, variance, min and max * Adapted from http://www.johndcook.com/blog/standard_deviation/ * @param {Object} p The previous value for the analysis * @param {Number} x New value to be included in analysis * @param {Number} i zero-based index of the current element being processed * @return {Object} New analysis including `x` */ export function statReduce({ mean = NaN, variance = NaN, min = +Infinity, max = -Infinity } /*: MathStat*/ , x /*: number*/ , i /*: number*/ ) /*: $ObjMap<MathStat, <V>(V) => $NonMaybeType<V>>*/ { mean = isNaN(mean) ? 0 : mean; x = x instanceof Date ? +x : x; const newMean = mean + (x - mean) / (i + 1); return { mean: newMean, min: x < min ? x : min, max: x > max ? x : max, variance: i < 1 ? 0 : (variance * i + (x - mean) * (x - newMean)) / (i + 1) }; } /** Convert date in the format YYYY-MM-DD to a number */ function dateToNumber(value /*: string*/ ) /*: number*/ { var _context2; const [year, month, day] = _mapInstanceProperty(_context2 = value.split('-')).call(_context2, Number); // Add 12 hours -> middle of day return new Date(year, month - 1, day).getTime() + 12 * 60 * 60 * 1000; } function numberToDate(value /*: number*/ ) /*: string*/ { const date = new Date(value); const YYYY = date.getFullYear(); const MM = leftPad(date.getMonth() + 1 + '', 2, '0'); const DD = leftPad(date.getDate() + '', 2, '0'); return `${YYYY}-${MM}-${DD}`; } //# sourceMappingURL=statistics.js.map