使用背景:
绘制尖峰平谷,24h 数据占比
<!-- 时间选择与预览组件 -->
<template>
<div>
<div class="legendCanvas">
<span class="legendCanvas-tit">时段预览</span>
<div class="legend-item" v-for="key in Object.keys(colorsMap)" :key="key">
<span class="icon" :style="{ background: colorsMap[key].color }"></span>
<span class="text">{{ colorsMap[key].text }}</span>
</div>
</div>
<canvas style="width: 950px; height: 50px;" ref="canvas"></canvas>
</div>
</template>
<script>
import moment from "moment";
import { add, divide, multiply } from "./main.js";
const colorsMap = {
1: {
color: '#F56C6C',
text: '尖',
},
2: {
color: '#FFB54D',
text: '峰',
},
3: {
color: '#67C23A',
text: '平',
},
4: {
color: '#BCD2FF',
text: '谷',
},
5: {
color: '#F3F3F3',
text: '空',
},
}
export default {
props: {
arrangeTimeArr: {
type: Array,
default: () => { }
},
},
data() {
return {
colorsMap,
ctx: '',
gap: 4, // 间隙
padding: 10, // 左右padding
rectH: 44,// 一块方格高度
fontColor: "rgba(0,0,0,0.45)",
fontSize: 22,
cWidth: '', // 内容宽度
outRectW: '', // 一块方格+gap 的宽度
rectW: '', // 一块方格宽度
scale: 24,
}
},
mounted() {
let arrangeTimeArr = [...this.arrangeTimeArr];
console.log(arrangeTimeArr, "arrangeTimeArr")
arrangeTimeArr.map((item) => {
item.color = colorsMap[item.flag].color;
item.start = item.startTime;
item.end = item.endTime;
})
const data = this.arrangeTimeStr(arrangeTimeArr);
this.drawInit();
for (const item of data) {
this.drawRect({ ...this.computedSize(item.start, item.end), color: item.color });
}
this.drawRadius();
},
methods: {
updateCanvas(paramsData) {
// console.log(paramsData, "paramsData9999999999999999999999999999999999999999")
let arrangeTimeArr = [...paramsData];
console.log(arrangeTimeArr, "arrangeTimeArr")
arrangeTimeArr.map((item) => {
item.color = colorsMap[item.flag].color;
item.start = item.startTime;
item.end = item.endTime;
})
const data = this.arrangeTimeStr(arrangeTimeArr);
this.drawInit();
for (const item of data) {
this.drawRect({ ...this.computedSize(item.start, item.end), color: item.color });
}
this.drawRadius();
},
/**
* 初始化
*/
drawInit() {
const canvas = this.$refs.canvas;
canvas.width = canvas.offsetWidth * 2;
canvas.height = canvas.offsetHeight * 2;
const ctx = canvas.getContext("2d");
const gap = 4; // 间隙
const padding = 10; // 左右padding
const cWidth = Math.floor(canvas.width - 10 * 2); // 内容宽度
const outRectW = Math.floor(cWidth / 24); // 一块方格+gap 的宽度
const rectW = outRectW - gap; // 一块方格宽度
const rectH = 44; // 一块方格高度
const fontColor = "rgba(0,0,0,0.45)";
const fontSize = 22;
const scale = 24;
this.ctx = ctx;
this.cWidth = cWidth; // 内容宽度
this.outRectW = outRectW; // 一块方格+gap 的宽度
this.rectW = rectW; // 一块方格宽度
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < scale; i++) {
const start = outRectW * i;
ctx.fillStyle = "#cccccc";
ctx.fillRect(start + padding, padding, rectW, rectH);
ctx.font = `${fontSize}px Arial`;
ctx.fillStyle = fontColor;
ctx.textAlign = "center";
ctx.fillText(i, start + padding, rectH + padding + fontSize);
}
},
/**
* 绘制区域
* @param {Object} param0
*/
drawRect({ start, end, color = "#ff0000" }) {
const rectW = this.rectW;
const ctx = this.ctx;
const padding = this.padding;
const rectH = this.rectH;
const gap = this.gap;
// console.log("=====", start, end);
let i = 0; // 防止无限循环
while (start <= end && i <= 100) {
ctx.fillStyle = color;
let fillW = rectW;
// let nextStart = start + fillW;
let nextStart = add(start , fillW);
if (nextStart <= end) {
if (nextStart % rectW !== 0) {
nextStart = nextStart - (nextStart % rectW);
fillW = nextStart - start;
}
} else {
// 累加不够一个方格的情况
nextStart = end;
if (nextStart % rectW !== 0) {
nextStart = nextStart - (nextStart % rectW);
// 不在在同一个区域方格内
if (nextStart > start) {
fillW = nextStart - start;
} else {
// 在在同一个区域方格内
nextStart = end;
fillW = end - start + 1;
}
} else {
// 在在同一个区域方格内
nextStart = end;
fillW = end - start + 1;
}
}
const accGap = parseInt(start / rectW, 10) * gap;
ctx.fillRect(start + accGap + padding, padding, fillW, rectH);
if(start === nextStart) break;
start = nextStart;
i++;
}
},
/**
* 圆角
* @param {Number} radius
*/
drawRadius(radius = 8) {
const canvas = this.$refs.canvas;
const ctx = this.ctx;
const rectH = this.rectH;
const padding = this.padding;
const outRectW = this.outRectW;
const scale = this.scale;
const gap = this.gap;
const drawEndX = outRectW * scale + padding - gap;
ctx.fillStyle = "#ffffff";
const leftTopP = new Path2D(
`M ${padding + radius} ${padding} L ${padding} ${padding} L ${padding} ${padding + radius
} C ${padding} ${padding + radius / 2} ${padding + radius / 2} ${padding} ${padding + radius
} ${padding}`
);
const leftBottomP = new Path2D(
`M ${padding} ${padding + rectH - radius} L ${padding} ${padding + rectH
} L ${padding + radius} ${padding + rectH} C ${padding + radius / 2} ${padding + rectH
} ${padding} ${padding + rectH - radius / 2} ${padding} ${padding + rectH - radius
}`
);
const rightTopP = new Path2D(
`M ${drawEndX - radius} ${padding} L ${drawEndX} ${padding} L ${drawEndX} ${padding + radius
} C ${drawEndX} ${padding + radius / 2} ${drawEndX - radius / 2
} ${padding} ${drawEndX - radius} ${padding}`
);
const rightBottomP = new Path2D(
`M ${drawEndX} ${padding + rectH - radius} L ${drawEndX} ${padding + rectH
} L ${drawEndX - radius} ${padding + rectH} C ${drawEndX - radius / 2} ${padding + rectH
} ${drawEndX} ${padding + rectH - radius / 2} ${drawEndX} ${padding + rectH - radius
}`
);
ctx.fill(leftTopP);
ctx.fill(leftBottomP);
ctx.fill(rightTopP);
ctx.fill(rightBottomP);
},
/**
* 时分字符串转canvas 长度
* @param {String} startTimeStr 开始时分:01:30
* @param {String} endTimeStr 结束时分:03:40
*/
computedSize(startTimeStr, endTimeStr) {
const rectW = this.rectW;
const totalRectAreaW = rectW * 24;
const start = multiply(
totalRectAreaW,
divide(moment.duration(startTimeStr, "HH:mm").valueOf(), 86400000)
);
const end = multiply(
totalRectAreaW,
divide(moment.duration(endTimeStr, "HH:mm").valueOf(), 86400000)
);
return {
start,
end
};
},
/**
* 数据格式化:结束比开始小的时候拆分开、排序、开始时间加1分
*/
arrangeTimeStr(list) {
return list
.reduce((acc, cur) => {
const startDur = moment.duration(cur.start, "HH:mm").valueOf();
const endDur = moment.duration(cur.end, "HH:mm").valueOf();
if (startDur > endDur) {
acc.push(
{
...cur,
end: "23:59"
},
{
...cur,
start: "00:00"
}
);
} else {
acc.push(cur);
}
console.log(acc, "acc")
return acc;
}, [])
.sort((a, b) => {
if (a.start < b.start) {
return -1;
}
if (a.start > b.start) {
return 1;
}
return 0;
})
.map((item, index, arr) => {
console.log(item, "99999")
const prev = arr[index - 1];
if (item.end === "24:00") {
if (item.start === "23:59") {
item.end = "00:00";
} else {
item.end = "23:59";
}
}
// if (prev && prev.end === item.start) {
// // item.start = moment(item.start, "HH:mm").add(1, "m").format("HH:mm");
// prev.end = moment(prev.end, "HH:mm").subtract(1, "m").format("HH:mm");
// return item;
// }
return item;
})
.filter((item) => !(item.start === "00:00" && item.end === "00:00"));
}
},
}
</script>
<style lang="less" scoped>
.legendCanvas {
display: flex;
margin-bottom: 13px;
padding-left: 5px;
&-tit {
color: rgba(0, 0, 0, 0.85);
font-size: 14px;
}
.legend-item {
margin-left: 16px;
.icon {
display: inline-block;
width: 14px;
height: 12px;
margin-right: 4px;
}
.text {
color: rgba(0, 0, 0, 0.85);
line-height: 22px;
}
}
}
</style>
使用示例:
<details-canvas :arrangeTimeArr="arrangeTimeArr"></details-canvas>
main.js
import BigNumber from "bignumber.js";
export function add(n1, n2) {
const c1 = new BigNumber(n1);
const c2 = new BigNumber(n2);
return c1.plus(c2).toNumber();
}
export function divide(n1, n2) {
const c1 = new BigNumber(n1);
const c2 = new BigNumber(n2);
return c1.div(c2).toNumber();
}
export function multiply(n1, n2) {
const c1 = new BigNumber(n1);
const c2 = new BigNumber(n2);
return c1.times(c2).dp(10).toNumber();
}