detailsCanvas绘制

该代码实现了一个Vue组件,用于在canvas上绘制24小时不同状态(尖峰、平谷等)的数据分布。组件接收时间数据,通过颜色映射进行可视化表示,并处理了时间数据的格式化和绘制逻辑,包括方格的初始化、区域填充和圆角绘制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用背景:

绘制尖峰平谷,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(); 
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值