当前位置: 首页 > news >正文

在vue项目中使用canvas实现甘特图

前言

上周部门组织前端代码审查,同事分析了一下使用canvas来实现甘特图的业务。大体常见是在el-table里显示甘特图,最开始是通过span标签来进行渲染的,但是存在一个比较严重的问题。假设一个插槽内渲染3个span标签,一行有20列,这就是60个span标签 ;如果一次查询100条数据就会有6000个span标签。这样造成的结果就是当数据量特别大时需要渲染特别多的dom元素,最终导致页面卡死,无法进行其他操作。

同事的优化方案是,用canvas来替换span 标签,这样一个插槽内只需要一个canvas标签,这样最终只需要渲染 列数 * 查询的总条数,这样减少了大量dom元素的渲染。

感觉方案还是非常不错的,就自己抽空写了一个简单的demo

实现

canvas实现甘特图其实最终还是转换成用canvas来画矩形。对canvas不熟悉的可以看一下我的这两篇文章

canvas基础

canvas动画

基本可以简单入门(我自己水平也就是皮毛,就是做一点简单的东西也要查一下😅)

效果图
在这里插入图片描述
demo代码

<template>
    <div>

        <el-table :data="tableData" style="width: 700px;" border>
            <el-table-column prop="monday" label="星期一">
                <template v-slot="scope">
                    <gantt-chart id="monday" :width="100" desc="星期一" descColor="#fff" :list="scope.row.monday">
                    </gantt-chart>
                </template>
            </el-table-column>
            <el-table-column prop="tuesday" label="星期二">
                <template v-slot="scope">
                    <gantt-chart id="tuesday" :width="100" desc="星期二" descColor="#fff" :list="scope.row.tuesday">
                    </gantt-chart>
                </template>
            </el-table-column>
            <el-table-column prop="wednesday" label="星期三">
                <template v-slot="scope">
                    <gantt-chart id="wednesday" :width="100" desc="星期三" descColor="#fff"
                        :list="scope.row.wednesday"></gantt-chart>
                </template>
            </el-table-column>
            <el-table-column prop="thursday" label="星期四">
                <template v-slot="scope">
                    <gantt-chart id="thursday" :width="100" desc="星期四" descColor="#fff" :list="scope.row.thursday">
                    </gantt-chart>
                </template>
            </el-table-column>
            <el-table-column prop="friday" label="星期五">
                <template v-slot="scope">
                    <gantt-chart id="friday" :width="100" desc="星期五" descColor="#fff" :list="scope.row.friday">
                    </gantt-chart>
                </template>
            </el-table-column>
            <el-table-column prop="saturday" label="星期六">
                <template v-slot="scope">
                    <gantt-chart id="saturday" :width="100" desc="星期六" descColor="#fff" :list="scope.row.saturday">
                    </gantt-chart>
                </template>
            </el-table-column>
            <el-table-column prop="sunday" label="星期日">
                <template v-slot="scope">
                    <gantt-chart id="sunday" :width="100" desc="星期日" descColor="#fff" :list="scope.row.sunday">
                    </gantt-chart>
                </template>
            </el-table-column>
        </el-table>
    </div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import ganttChart from './gantt-chart.vue';

const tableData = ref([
    {
        monday: [
            {
                width: 30,
                color: 'red'
            },
            {
                width: 70,
                color: 'blue'
            }
        ],
        tuesday: [
            {
                width: 30,
                color: 'red'
            },
            {
                width: 20,
                color: 'blue'
            },
            {
                width: 50,
                color: 'gray'
            }
        ],
        wednesday: [
            {
                width: 30,
                color: 'red'
            },
            {
                width: 70,
                color: 'blue'
            }
        ],
        thursday: [
            {
                width: 30,
                color: 'red'
            },
            {
                width: 40,
                color: 'blue'
            },
            {
                width: 30,
                color: 'yellow'
            }
        ],
        friday: [
            {
                width: 30,
                color: 'red'
            },
            {
                width: 70,
                color: 'blue'
            }
        ],
        saturday: [
            {
                width: 50,
                color: 'blue'
            },
            {
                width: 50,
                color: 'pink'
            }
        ],
        sunday: [
            {
                width: 30,
                color: 'red'
            },
            {
                width: 70,
                color: 'blue'
            }
        ]
    }
]);
</script>
<style scoped lang="scss">
:deep(.el-table .cell) {
    padding: 0;
}

:deep(.el-table--border .el-table__cell:first-child .cell) {
    padding-left: 0;
}
</style>

组件代码

本来是还要考虑界限问题的,每一个小矩形会不会超出总的容器宽度。后来觉得没必要搞的这么麻烦,本来就是根据实际业务来实现的,完全可以使用标准的数据格式。

<template>
    <div>
        <canvas :id="id" :width="width" :height="height"></canvas>
    </div>
</template>

<script setup lang="ts">
import { ref, onMounted, nextTick } from 'vue';

// 每次绘制矩形时的偏移量
const residueWidth = ref(0);

interface ganttChartItem {
    // 宽度
    width: number,
    // 颜色
    color: string
}

interface Props {
    // id
    id:string
    // 宽度
    width: number,
    // 高度
    height?: number,
    // 是否初始化
    init?: boolean,
    // 子项
    list?: Array<ganttChartItem>,
    // 是否显示描述
    desc?:string
    // 描述颜色
    descColor?:string
}

const props = withDefaults(defineProps<Props>(), {
    // 高度默认20
    height: 20,
    // 默认初始化
    init: true,
    // 子项默认为空
    list: () => [],
    // 是否显示描述
    desc: '',
    // 描述颜色
    descColor: ''
});

onMounted(() => {
    draw();
});

// 绘制
const draw = () => {
    // 获取对象
    const c = document.getElementById(props.id);
    // 获取2d画布
    const context = c.getContext('2d');
    if (props.list.length > 0) {
        // 要加上nextTick不然会先绘制文本,导致文本在矩形下面
        nextTick(() => {
            for (let i = 0; i < props.list.length; i++) {
            // 循环绘制,这里不考虑宽度是否超出的问题,默认数据都是合理的
                drawRectangle(context, props.list[i].color, residueWidth.value, props.list[i].width);
                residueWidth.value += props.list[i].width;
            }
            drawDesc(context);
        });
    }
};
// 绘制矩形
const drawRectangle = (context, color: string, x: number, width: number) => {
    context.fillStyle = color; // 指定填充颜色
    context.fillRect(x, 0, width, props.height); // 填充
};

// 绘制描述
const drawDesc = (context) => {
    if (props.desc && props.descColor) {
        context.fillStyle = props.descColor;
        context.font = parseInt(props.height / 2) + 'px';
        // (text,x,y)文字内容、绘制的其实位置
        context.fillText(props.desc, 5, props.height * 0.7);
    }
};
</script>

相关文章:

  • 【C++】之模板进阶
  • 100天精通Python(数据分析篇)——第58天:Pandas读写数据库(read_sql、to_sql)
  • Bean的生命周期
  • 哈希桶(详解创建)
  • 回归预测 | MATLAB实现SSA-BP多输入单输出回归预测
  • 【雅思备考】听说读写攻略 | 雅思核心词汇之科技类
  • Python-列表,从基础到进阶用法大总结,进来查漏补缺
  • JDBC模拟SQL注入和避免SQL注入
  • flink在企业IT架构中如何定位-在选型流批一体技术与大数据架构时的避坑指南
  • JUC并发编程之CompletableFuture基础用法
  • SpringBoot+Mybatis-Plus多数据源使用
  • Colab-免费GPU算力
  • 【CH559L单片机】串口下载程序说明
  • CMake中macro的使用
  • windows利用msys2安装minGW64
  • 网络传输文件的问题
  • [Vue CLI 3] 配置解析之 css.extract
  • angular组件开发
  • Cumulo 的 ClojureScript 模块已经成型
  • Java 网络编程(2):UDP 的使用
  • PAT A1050
  • Python学习之路13-记分
  • seaborn 安装成功 + ImportError: DLL load failed: 找不到指定的模块 问题解决
  • Spring Cloud中负载均衡器概览
  • 测试开发系类之接口自动化测试
  • 前端面试之闭包
  • 如何借助 NoSQL 提高 JPA 应用性能
  • 深入浅出webpack学习(1)--核心概念
  • 世界上最简单的无等待算法(getAndIncrement)
  • 小程序滚动组件,左边导航栏与右边内容联动效果实现
  • 一道闭包题引发的思考
  • 责任链模式的两种实现
  • 正则与JS中的正则
  • 最简单的无缝轮播
  • # 再次尝试 连接失败_无线WiFi无法连接到网络怎么办【解决方法】
  • #Lua:Lua调用C++生成的DLL库
  • (70min)字节暑假实习二面(已挂)
  • (二)正点原子I.MX6ULL u-boot移植
  • (附源码)springboot电竞专题网站 毕业设计 641314
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (免费领源码)python+django+mysql线上兼职平台系统83320-计算机毕业设计项目选题推荐
  • (四)库存超卖案例实战——优化redis分布式锁
  • (四)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • (已解决)vue+element-ui实现个人中心,仿照原神
  • (原)记一次CentOS7 磁盘空间大小异常的解决过程
  • (转)shell调试方法
  • (转)原始图像数据和PDF中的图像数据
  • . ./ bash dash source 这五种执行shell脚本方式 区别
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET 4 并行(多核)“.NET研究”编程系列之二 从Task开始
  • .net Signalr 使用笔记
  • .Net 高效开发之不可错过的实用工具
  • .net/c# memcached 获取所有缓存键(keys)
  • @ComponentScan比较
  • @Conditional注解详解