使用canvas实现一个二叉树(vue+ts)

使用canvas实现一个二叉树(vue+ts)


在这里插入图片描述

// 子组件 tree.vue
<template>
    <div class="binary-tree-wrap">
        <canvas :id="id"></canvas>
    </div>
</template>
<script lang="ts">
	import {
        Component,
        Vue,
        Prop
    } from "vue-property-decorator"
    @Component({})
    export default class About extends Vue {
		@Prop({
            required: true,
            default: '1'
        }) id!: string // 接收父组件的传值id

        @Prop({
            required: true
        }) data!: any // 接收父组件的传值 所要渲染的数据

        @Prop({
            required: false,
            default: '#1D716D'
        }) color!: '' // 接收父组件的传值 组件的颜色

        h: number = 100 // canvas的高度

        w: number = 100 // canvas的宽度

		mounted() {
			this.initCanvas()
		}
		
		initCanvas() {
			let box = document.getElementsByClassName('binary-tree-wrap')[0] as HTMLElement
            this.h = box.offsetHeight
            this.w = box.offsetWidth
            let canvas = document.getElementById(this.id) as HTMLCanvasElement;
            // 设置canvas的宽高为容器的宽高
            canvas.width = this.w
            canvas.height = this.h
			
			if (canvas.getContext) {
                let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
                // 绘制左侧name 在图层下面 获取字体高度
                let LineH = line.height
                this.roundText(
                    ctx, 
                    this.data.name, 
                    rectangle1.paddingLR, 
                    this.h / 2,
                    {
                       baseLine: 'middle' // 字体基线对其
                    }
                )
                let nameBox = this.getTextWidth(ctx, this.data.name)
                let textHeight = nameBox.height
                let y = Math.round(this.h / 2 - textHeight / 2)
                this.roundedRect(
                    ctx,
                    0,
                    y - rectangle1.paddingTB,
                    nameBox.width + rectangle1.paddingLR * 2,
                    textHeight + rectangle1.paddingTB * 2,
                    10,
                    {}
                )
                // 绘制左侧name
                this.roundText(
                    ctx, 
                    this.data.name, 
                    rectangle1.paddingLR, 
                    this.h / 2,
                    {
                       baseLine: 'middle' // 字体基线对其
                    }
                )

                // // 绘制说明框架
                let circleAreaH = Math.round(this.h / this.data.children.length)
                let regularBoxH = this.h - (circleAreaH - Math.round(circleAreaH / 3) * 2) + 10
                // 绘制圆后长方形说明
                // 获取所有说明中长度最长的一个
                let regularBoxWs: number[] = []
                // 获取所有explain中长度最长的一个
                let explainBoxWs: number[] = []
                this.data.children.forEach((item: any) => {
                    regularBoxWs.push(this.getTextWidth(ctx, item.regular).width)
                    explainBoxWs.push(this.getTextWidth(ctx, item.explain).width)
                });
                let regularBoxW = Math.max(...regularBoxWs)
                let regularX = this.w - (regularBoxW + ( rectangle2.paddingLR * 2 ))
                let explainBoxW = Math.max(...explainBoxWs)
                // 绘制最右侧圆角长方形
                this.roundedRect(
                    ctx,
                    regularX,
                    circleAreaH - Math.round(circleAreaH / 3) * 2 - 20,
                    regularBoxW + ( rectangle2.paddingLR * 2 ),
                    regularBoxH,
                    10,
                    {
                        background: rectangle2.background
                    }
                )
                let nameLineW = this.w / 10
                let circleR = Math.round(circleAreaH / 4)
                // 绘制name横向长条
                this.roundLine(
                    ctx, 
                    nameBox.width + rectangle1.paddingLR * 2,
                    (this.h / 2),
                    nameBox.width + rectangle1.paddingLR * 2 + nameLineW - circleR,
                    (this.h / 2),
                    {
                        lineWidth: 5,
                        strokeStyle: this.color
                    }
                )
                // 绘制圆e3
                let circleX = Math.round(this.w / 2)
                // let circleR = Math.round(circleAreaH / 4)
                for (let i = 0, len = this.data.children.length; i < len; i++) {
                    let circleY = Math.round(circleAreaH / 2 + circleAreaH * i)
                    if(i === 0 || (i === len - 1)){
                        this.roundLine(
                            ctx, 
                            nameBox.width + rectangle1.paddingLR * 2 + nameLineW - circleR,
                            (this.h / 2 - LineH / 2),
                            nameBox.width + rectangle1.paddingLR * 2 + nameLineW - circleR,
                            (this.h - circleAreaH * i) - circleAreaH / 2, // 最高 和 最低点
                            {
                                lineWidth: 5,
                                strokeStyle: this.color
                            }
                        )
                    }

                    // 圆左侧横线
                    this.roundLine(
                        ctx, 
                        nameBox.width + rectangle1.paddingLR * 2 + nameLineW - circleR,
                        (this.h - circleAreaH * i) - circleAreaH / 2,
                        circleX - circleR,
                        (this.h - circleAreaH * i) - circleAreaH / 2, 
                        {
                            lineWidth: 5,
                            strokeStyle: this.color
                        }
                    )

                   // 圆内 regular
                    this.roundText(
                        ctx,
                        this.data.children[i].regular,
                        this.w - (regularBoxW +  rectangle2.paddingLR),
                        circleY + 6,
                        {}
                    )
                    // 圆
                    this.roundArc(
                        ctx,
                        circleX,
                        circleY,
                        circleR
                    )
                     

                    // 圆后 No
                    this.roundText(
                        ctx,
                        this.data.children[i].No,
                        Math.round(this.w / 2 - 5),
                        circleY + 6,
                        {}
                    )
                    // 圆后 explain
                    let spaceX = regularX + 5 - circleX - circleR - explainBoxW
                    let explainX = circleX + spaceX / 2 + circleR
                    this.roundText(
                        ctx,
                        this.data.children[i].explain,
                        explainX,
                        circleY + 6,
                        {
                            fontColor: 'black'
                        }
                    )
                    
                }
		}
		// 获得文字宽度
        getTextWidth(ctx: CanvasRenderingContext2D, text: string) {
            let font = ctx.measureText(text) as any
            return {
                width: Math.round(font.width),
                height: Math.round(font.actualBoundingBoxDescent - font.actualBoundingBoxAscent)
            }
        }
        // text
        roundText(ctx: CanvasRenderingContext2D, text: string, x: number, y: number, style: fontStyle ) {
            
            ctx.font = style.fontColor || '20px Microsoft YaHei'; // 字体大小
            ctx.fillStyle = style.fontColor || '#fff'; // 字体颜色
            ctx.textBaseline = style.baseLine || "alphabetic" as any; // 字体对齐方式

            ctx.fillText(text, x, y); // 填充字体
        }
        // 绘制带圆角的长方形
        roundedRect(ctx: CanvasRenderingContext2D, x: number, y: number, width: number, height: number, radius:
            number, style: rectStyle) {
            ctx.beginPath();
            ctx.moveTo(x, y + radius);
            ctx.lineTo(x, y + height - radius);
            ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
            ctx.lineTo(x + width - radius, y + height);
            ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
            ctx.lineTo(x + width, y + radius);
            ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
            ctx.lineTo(x + radius, y);
            ctx.quadraticCurveTo(x, y, x, y + radius);
            ctx.fillStyle = style.background || this.color
            ctx.fill()

        }
        // 绘制圆
        roundArc(ctx: CanvasRenderingContext2D, x: number, y: number, r: number) {
            ctx.beginPath();
            ctx.arc(
                x,
                y,
                r,
                0,
                2 * Math.PI
            );
            ctx.fillStyle = this.color;
            ctx.fill()
            ctx.closePath()
        }

        roundLine(ctx: CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number, style: lineStyle) {
            ctx.beginPath(); // 开始画线
            ctx.moveTo(x1, y1); // 起点
            ctx.lineTo(x2, y2); // 重点

            ctx.strokeStyle = style.strokeStyle || "#1D716D" // 线条颜色
            ctx.lineWidth = style.lineWidth || 2 // 线条宽度
            ctx.lineCap = style.lineJoin || 'square' as any// 线条间接合处的样式

            ctx.stroke(); // 描边
        }
	}
</script>
<style lang="less">
    .binary-tree-wrap {
        height: 100%;
        width: 100%;
    }
</style>

// 父组件引用
<template>
    <div class="operatSitus-wrap">
        <div class="over-title">运维情况</div>
        <BinaryTree class="operatSitus-data-wrap" id="binaryTreeId" :data="operatSitusData" :color="treeColor"/>
    </div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import BinaryTree from '@/components/common/binaryTree.vue'
@Component({
    components: { BinaryTree }
})
export default class About extends Vue {

    operatSitusData: any = {
        name: '叉车(沪A98222)',
        children: [{
                No: 1,
                explain: '维护',
                regular: '年检每年一次'
            },
            {
                No: 2,
                explain: '保养',
                regular: '保养每月一次'
            },
            {
                No: 4,
                explain: '巡检',
                regular: '巡检每周一次'
            }
        ],
        option: {
            line: {
                height: 10
            },
            rectangle1: {
                paddingTB: 20, // padding 上下
                paddingLR: 10 // padding 左右
            },
            rectangle2: {
                paddingLR: 10, // padding 左右
                background: '#0078DB'
            },
        }
    }

    treeColor:string = '#0078DB'
}
</script>
<style lang="less">
.operatSitus-wrap {
    width: 100%;
    height: 100%;
    background:#ffffff;
    border: 1px solid #ffffff;
    border-radius:6px;
    .operatSitus-data-wrap {
        width: 95%;
        height: 83%;
        margin: auto;
    }
}
</style>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值