在项目中需要从月份统计结果中截取指定范围的子序列用于绘制折线图。
假设统计结果数组为
const series = [
{ name: '2020-04', value: 3 },
{ name: '2020-06': value: 5 },
{ name: '2020-08': value: 4 },
{ name: '2021-02': value: 10 },
{ name: '2021-03': value: 15 },
{ name: '2021-05': value: 6 },
]
数据特点是:
- 已经按照年份-月份先后顺序排序
- 部分月份信息未给出,需要补零
要解决的问题是,给定时间范围start
和end
,截取出包含end
月份在内的子序列,对缺少的月份补零。
因为原序列是有序的,所以我们只需要确定起始位置和结束位置的索引即可。为了保证数组首尾两个位置能够被统一识别,我们在数组首尾添加两个元素:首部添加的元素一定比start
的月份小,尾部添加的元素一定比end
的月份大
const exSeries = [
{ name: `${start.getFullYear()}-${start.getMonth() - 1}`, value: 0 },
...series,
{ name: `${end.getFullYear()}-${end.getMonth() + 1}`, value: 0 },
]
这样做的好处是,使用findIndex
方法查找索引永远不会返回-1
:
const startIndex = exSeries.findIndex(d => new Date(d.name) >= start)
const endIndex = exSeries.findIndex(d => new Date(d.name) > end)
然后截取出子序列:
const truncatedSeries = series.slice(startIndex, endIndex)
然后对缺失的月份补零:
import _ from 'lodash'
/** 将给定的年份和月份拼凑成 yyyy-mm 的字符串形式 */
const YYYYMM = (y: number, m: number) => {
const m$1 = m.toString().length === 1 ? `0${m.toString()}` : m
return `${y}-${m$1}`
}
/** 穷举出指定范围内的所有 yyyy-mm 形式的日期 */
const listMonth = (start: Date, end: Date) => {
const sy = start.getFullYear()
const ey = end.getFullYear()
const sm = start.getMonth()
const em = end.getMonth()
// 年份相同看月份
if (sy === ey) {
return _.times(em - sm + 1).map(m => YYYYMM(sy, m + sm + 1))
}
// 年份差1
if (ey - sy === 1) {
return [
..._.times(12 - sm).map(m => YYYYMM(sy, m + sm + 1)),
..._.times(em + 1).map(m => YYYYMM(ey, m + 1)),
]
}
// 年份差2及以上
if (ey - sy >= 2) {
return [
..._.times(12 - sm).map(m => YYYYMM(sy, m + sm + 1)),
..._.flatten(
_.times(ey - sy - 1).map(y => _.times(12).map(m => YYYYMM(y + ey - 2, m + 1)))
),
..._.times(em + 1).map(m => YYYYMM(ey, m + 1)),
]
}
throw Error('Incorrect start or end')
}
let index = 0
const fullSeries = []
listMonth(start, end).forEach(mstr => {
if (index < series.length && mstr === series[index].name) {
fullSeries.push(series[index++])
} else {
fullSeries.push({ name: mstr, value: 0 })
}
})
最后补全的fullSeries
可用于绘制时间线完整的图表。
评论区