使用canvas实现一个二叉树(vue+ts)
<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
@Prop({
required: true
}) data!: any
@Prop({
required: false,
default: '#1D716D'
}) color!: ''
h: number = 100
w: number = 100
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.width = this.w
canvas.height = this.h
if (canvas.getContext) {
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
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,
{}
)
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[] = []
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)
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
}
)
let circleX = Math.round(this.w / 2)
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
}
)
this.roundText(
ctx,
this.data.children[i].regular,
this.w - (regularBoxW + rectangle2.paddingLR),
circleY + 6,
{}
)
this.roundArc(
ctx,
circleX,
circleY,
circleR
)
this.roundText(
ctx,
this.data.children[i].No,
Math.round(this.w / 2 - 5),
circleY + 6,
{}
)
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)
}
}
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,
paddingLR: 10
},
rectangle2: {
paddingLR: 10,
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>