You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
dashboard/src/components/DateGroupRadio/date.js

248 lines
10 KiB
JavaScript

import moment from 'moment';
import { fixTo2Decimals, groupBy, isEmpty, sortBy } from '../../utils/commons';
export const datePartOptions = [
{ label: '日', value: 'day' },
{ label: '周', value: 'week' },
{ label: '月', value: 'month' },
{ label: '季', value: 'season' },
{ label: '年', value: 'year' },
];
function generateDateRange(minDate, maxDate, dateType = "day") {
// console.log('🔁', minDate, maxDate, dateType);
const dates = [];
const rangeForUnit = {};
const current = moment(minDate).startOf(dateType);
const end = moment(maxDate);
while (current.isSameOrBefore(end)) {
let formatted;
const currentRange = current.format("YYYY-MM-DD");
switch (dateType) {
case "day":
formatted = current.format("YYYY-MM-DD");
current.add(1, "days");
break;
case "week":
// formatted = `Week ${current.week()} ${current.year()}`;
formatted = `${current.weekYear()}-W${String(current.week()).padStart(2, '0')}`;
current.add(1, "weeks");
break;
case "month":
formatted = current.format("YYYY-MM");
current.add(1, "months");
break;
case "quarter":
case "season":
// formatted = `Q${current.quarter()} ${current.year()}`;
formatted = `${current.year()}-${String(current.quarter()).padStart(2, 'Q')}`;
current.add(1, "quarters");
break;
case "year":
formatted = current.format("YYYY");
current.add(1, "years");
break;
}
dates.push(formatted);
rangeForUnit[formatted] = currentRange;
// rangeForUnit.push({formatted: currentRange});
}
return [dates, rangeForUnit];
}
export const datePartMethod = {
'day': (date) => {
return { 'dateKey': date, 'groupKey': date, date };
},
'week': (date, ...args) => {
const dateO = moment(date);
const year = dateO.weekYear();
const week = dateO.week();
// const weekOfMonth = dateO.week() - moment(date).startOf('month').week() + 1;
// const key = `W${String(week).padStart(2, '0')}`;
const dateKey = `${year}-W${String(week).padStart(2, '0')}`;
return { dateKey, 'groupKey': dateKey, date };
},
'season': (date) => {
const dateO = moment(date);
// const key = dateO.format('YYYY-Q');
const key = `${dateO.year()}-${String(dateO.quarter()).padStart(2, 'Q')}`;
const dateKey = `${dateO.year()}-${String(dateO.quarter()).padStart(2, 'Q')}`;
return { dateKey, 'groupKey': key, date };
},
'month': (date) => {
const dateO = moment(date);
const key = dateO.format('YYYY-MM');
const dateKey = dateO.format('YYYY-MM');
return { dateKey, 'groupKey': key, date };
},
'year': (date) => {
const dateO = moment(date);
const key = dateO.format('YYYY');
const dateKey = dateO.format('YYYY');
return { dateKey, 'groupKey': key, date };
},
};
/**
*
* @param {Array} data 结果数组
* @param {*} dateType 切换的日期类型
* @param {Object} mapper 映射结果字段
* @returns {Object} { data, avg }
* * data: 转换后的数组
* * avg: 平均值
*/
export const parseDateType = (data, dateType = 'day', { dateKey, valueKey, seriesKey, _f }, range = []) => {
if (isEmpty(data)) {
return { data: [], avgVal: 0 };
}
const _calcF = {
'avg': (sum, len) => sum / len,
};
// if (data.length ===0 ) console.groupCollapsed('📙');
// else console.group('📅');
// console.log('💠', dateKey, valueKey, seriesKey, range);
const rawDates = [...new Set(data.map((c) => c[dateKey]))].sort();
const _minDate = rawDates[0];
const _maxDate = rawDates[rawDates.length - 1];
const [minDate, maxDate] = range;
const [solidDateArr, solidDateStartsObj] = generateDateRange(minDate || _minDate, maxDate || _maxDate, dateType);
// // Diff: elements in solidDateArr but not in rawDates
// const diffKey = [...new Set(solidDateArr.filter((x) => !new Set(rawDates).has(x)))];
// console.log('⏩', rawDates);
// console.log('💫 solidDateArr', solidDateArr, solidDateStartsObj);
// console.log('⛔', diffKey);
const seriesDataMapped = data.reduce((r, v) => {
const _k = v[seriesKey];
(r[_k] || (r[_k] = [])).push(v);
return r;
}, {});
const everySeries = Object.keys(seriesDataMapped).reduce((rs, _series) => {
const e = seriesDataMapped[_series].reduce((r, v) => {
const datePart = datePartMethod[dateType](v[dateKey]);
const mergeKey = `${datePart.groupKey}@${_series}`;
(r[mergeKey] || (r[mergeKey] = [])).push({ ...v, 'dateKey': datePart.dateKey, 'groupKey': datePart.groupKey, datePart });
return r;
}, {});
return { ...rs, ...e };
}, {});
// console.log('🌗 raw', everySeries);
const resultKeys = Object.keys(everySeries);
resultKeys.sort();
const dateArr = [];
const groupSum = resultKeys.reduce((a, key) => {
const [_dateKey, _seriesKey] = key.split('@');
dateArr.push(_dateKey);
const containDate = everySeries[key].map((ele) => ele.datePart.date);
const containDateM = [...new Set(containDate)].map((ele) => moment(ele));
const min = moment.min(containDateM).format('YYYY-MM-DD');
const max = moment.max(containDateM).format('YYYY-MM-DD');
const dateRangeStr = min === max ? min : `${min}~${max}`;
const dateRange = [min, max];
const summaryVal = everySeries[key].reduce((rows, row) => rows + row[valueKey], 0);
const retValue = _f === 'sum' ? summaryVal : _calcF(summaryVal, everySeries[key].length);
a.push({ groupKey: key, value: fixTo2Decimals(retValue), dateKey: dateRangeStr, dateRange, containDate, [seriesKey]: _seriesKey, [dateKey]: _dateKey });
return a;
}, []);
const avgDiv = [...new Set(dateArr)].length;
const avgVal = groupSum.length !== 0 ? groupSum.reduce((s, c) => s + c.value, 0) / avgDiv : 0;
// 补全连续的时间轴
const fakeSeries = isEmpty(groupSum) ? '' : groupSum[0][seriesKey];
// Diff: elements in solidDateArr but not in dateArr
const emptyDateUnits = [...new Set(solidDateArr.filter((x) => !new Set(dateArr).has(x)))];
// console.log('🌕 real ret', structuredClone(groupSum));
// console.log('🌕 date units',dateArr);
// console.log('🌕 [?]', emptyDateUnits);
emptyDateUnits.forEach(dateUnit => {
const unitStartDate = solidDateStartsObj[dateUnit];
groupSum.push({
groupKey: dateUnit + '@' + fakeSeries,
value: null, // 0 undefined
dateKey: unitStartDate,
dateRange: [unitStartDate,unitStartDate],
containDate: [unitStartDate],
[seriesKey]: fakeSeries,
[dateKey]: dateUnit,
});
});
groupSum.sort(sortBy(dateKey));
// console.log('☀ ret ', groupSum);
// console.groupEnd();
return { data: groupSum, avgVal };
};
/**
*
* @param {Array} dataRaw 结果数组
* @param {*} dateGroup 切换的日期类型, radio onchange 的值
* @param {Object} dataMapper 映射数据集字段 { data1, data2 }
* @param {Object} mapper 映射结果字段 { dateKey, valueKey, _f }
* * dateKey: 日期. 'ApplyDate'
* * valueKey: 结果. 'orderCount'
* * seriesKey: 序列. 'WebCode'
* * _f: 计算方法. 'sum' | 'avg
* @author LYT
*/
export const resultDataCb = (dataRaw, dateGroup, { data1, data2 }, { range1, range2, ...fieldMapper }, cb) => {
const _data1 = data1 ? dataRaw[data1] : dataRaw;
const _data2 = data2 ? dataRaw[data2] : [];
const parse1 = parseDateType(_data1, dateGroup, fieldMapper, range1);
const parseData1 = parse1.data.map((ele) => ({
[fieldMapper.dateKey]: ele[fieldMapper.dateKey] === 'Invalid date' ? '空日期' : ele[fieldMapper.dateKey],
[fieldMapper.valueKey]: ele.value,
[fieldMapper.seriesKey]: ele[fieldMapper.seriesKey],
groups: _data1[0].groups,
dateKey: ele.dateKey,
dateRange: ele.dateRange,
dateGroup: ele[fieldMapper.dateKey],
}));
const parse2 = parseDateType(_data2, dateGroup, fieldMapper, range2);
const parseData2 = parse2.data.map((ele) => ({
[fieldMapper.dateKey]: ele[fieldMapper.dateKey] === 'Invalid date' ? '空日期' : ele[fieldMapper.dateKey],
// [fieldMapper.dateKey]: ele.groupKey,
[fieldMapper.valueKey]: ele.value,
[fieldMapper.seriesKey]: ele[fieldMapper.seriesKey],
groups: _data2[0].groups,
dateKey: ele.dateKey,
dateRange: ele.dateRange,
dateGroup: ele[fieldMapper.dateKey],
}));
const data1KeyMapped = parseData1.reduce((r, v) => {
const _k = v[fieldMapper.dateKey];
const allDate0 = moment.min([...new Set((r[_k] || []).concat(moment(v.dateRange[0])))]);
r[_k] = [allDate0];
return r;
}, {});
const data1KeyMappedStr = Object.keys(data1KeyMapped).reduce((r, v) => ({...r, [v]: data1KeyMapped[v][0].format('YYYY-MM-DD')}), {});
const data2KeyMapped = parseData2.reduce((r, v) => {
const _k = v[fieldMapper.dateKey];
const allDate0 = moment.min([...new Set((r[_k] || []).concat(moment(v.dateRange[0])))]);
r[_k] = [allDate0];
return r;
}, {});
const data2KeyMappedStr = Object.keys(data2KeyMapped).reduce((r, v) => ({...r, [v]: data2KeyMapped[v][0].format('YYYY-MM-DD')}), {});
const groupBykeyD1 = Object.keys(groupBy(parseData1, ele => ele[fieldMapper.dateKey]));
const groupBykeyD2 = Object.keys(groupBy(parseData2, ele => ele[fieldMapper.dateKey]));
const keyMapped = {
...groupBykeyD2.reduce((obj, k, i) => ({...obj, [k]: groupBykeyD1[i] || k}), {})
};
const reindexData2 = parseData2.map((ele, index) => ({
...ele,
[fieldMapper.dateKey]: keyMapped[ele[fieldMapper.dateKey]],
dateKey: ele.dateKey,
}));
const retData = [].concat(parseData1, reindexData2 ).map(ele => ({...ele,
[fieldMapper.dateKey]: data1KeyMappedStr[ele[fieldMapper.dateKey]] === 'Invalid date' ? '空日期' : (data1KeyMappedStr[ele[fieldMapper.dateKey]] || data2KeyMappedStr[ele[fieldMapper.dateKey]])}));
const avg1 = parse1.avgVal;
// console.log('callback','\ndateGroup', dateGroup,
// '\nretData', retData,
// '\navg1', avg1,
// '\nparse2', parse2.avgVal,
// '\ndata1KeyMappedStr', data1KeyMappedStr,
// '\ndata2KeyMappedStr', data2KeyMappedStr
// );
return cb(dateGroup, retData, avg1, parse2.avgVal);
};