1,竖向树状图
<template>
<div class="about">
<div>
canvas绘图
</div>
<div class="cavas">
<canvas ref='canvas' width="600" height="1000"></canvas>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, createTextVNode } from "vue"
let count = ref(0)
const handlerbtn = () => {
count.value++
console.log(count)
}
// 进线 nodetype = 1,主母线 2 母线 3 变压器4 回路 5
// transformGroup 变压器 2 双绕组,3 三绕组
let obj = [
{
nodeName: "进线",
nodeType: 1,
voltage: "220kV",
id: 1,
pid: 0,
childern: [
{
nodeName: "母线1",
nodeType: "2",
voltage: "220kV",
id: 2,
pid: 1,
childern:
[
{
nodeName: "变压器1",
nodeType: 4,
transformGroup: 3,
id: 3,
pid: 2,
childern: [
{ nodeName: "母线1", nodeType: 3, voltage: '110kV', id: 4, pid: 3, childern: [{ nodeName: "回路", nodeType: 5, voltage: '110kV', id: 5, pid: 3 }] },
{ nodeName: "回路", nodeType: 5, voltage: '110kV', id: 5, pid: 3 }
]
}
]
},
{
nodeName: "母线1",
nodeType: "2",
voltage: "220kV",
id: 2,
pid: 1,
childern:
[
{
nodeName: "变压器1",
nodeType: 4,
transformGroup: 2,
id: 3,
pid: 2,
childern: [
{ nodeName: "母线1", nodeType: 3, voltage: '110kV', id: 4, pid: 3, childern: [{ nodeName: "回路", nodeType: 5, voltage: '110kV', id: 5, pid: 3 }] },
{ nodeName: "回路", nodeType: 5, voltage: '110kV', id: 5, pid: 3 }
]
},
{
nodeName: "变压器1",
nodeType: 4,
transformGroup: 3,
id: 3,
pid: 2,
childern: [
{ nodeName: "母线1", nodeType: 3, voltage: '110kV', id: 4, pid: 3, childern: [] },
{ nodeName: "回路", nodeType: 5, voltage: '110kV', id: 5, pid: 3 },
{ nodeName: "回路", nodeType: 5, voltage: '110kV', id: 5, pid: 3 }
]
},
]
}
]
}
]
const canvas = ref()
console.log(canvas, 'canvars')
// 会在竖线 n为循环次数 (总的循环几次,表示有几条横向数据)
onMounted(() => {
const ctx = canvas.value.getContext('2d');
ctx.strokeStyle = 'red';
// 1,计算当前分支上的 子元素的个数,根据个数计算出父元素的位置
const getAllCount = (node) => {
// 第一次传的有childern
console.log(node, 'node')
let count = 0
node.childern.reduce((pre, cur) => {
console.log(cur, 'cur')
count = pre + (cur.childern && cur.childern.length > 0 ? getAllCount(cur) : 1)
return count
}, 0)
return count
}
// 2,确定父元素的起始点,划线
const darwLine = (X, Y, childX, chidY, type, title) => {
// X,Y 父元素起始位置
// childX,ChidY, 子元素起始位置
// type 类型 变压器,回路
let lineWidth = 50
let lineHeight = 10
ctx.beginPath()
ctx.moveTo(X, Y - lineHeight / 2)
ctx.lineTo(X + lineWidth, Y - lineHeight / 2)
ctx.lineTo(childX, chidY - lineHeight / 2)
ctx.lineTo(childX + lineWidth, chidY - lineHeight / 2)
// 判断type
if (type == 1) {
// 变压器1
ctx.arc(childX + 20, chidY - lineHeight / 2, 10, 2 * Math.PI, 0)
ctx.arc(childX + 30, chidY - lineHeight / 2, 10, 2 * Math.PI, 0)
} else if (type == 2) {
// 变压器2
ctx.arc(childX + 20, chidY - lineHeight / 2, lineHeight, 2 * Math.PI, 0)
ctx.arc(childX + 30, chidY - lineHeight, lineHeight, 2 * Math.PI, 0)
ctx.arc(childX + 30, chidY - lineHeight / 4, lineHeight, 2 * Math.PI, 0)
} else if (type == 3) {
// 回路
ctx.fillStyle = "red";
ctx.fillRect(childX + 10, chidY - lineHeight, 30, lineHeight)
}
else {
ctx.lineTo(childX, chidY - lineHeight / 2)
ctx.lineTo(childX + lineWidth, chidY - lineHeight / 2)
}
// 标注名称
ctx.strokeStyle = 'red';
let { width: textWidth } = ctx.measureText(title);
ctx.fillText(title, childX + textWidth / 3, chidY - 12, textWidth);
ctx.stroke()
}
// darwLine(100, 200, 150, 400, 3)
//循环数据,计算
const render = (node, startX, startY, type) => {
// 定义基本信息
let lineWidth = 50
let lineHeight = 40
let linemargin = 10 //间距
// 绘制第一个横线
// darwLine(startX, startY, childX, chidY, type)
if (node.childern && node.childern.length > 0) {
let count = getAllCount(node)
let start = startY
// + count * lineHeight //第一个元素起始位置Y
node.childern.forEach((item, index) => {
let childcount = 0 //计算第一个子元素的位置
// 当前元素的横坐标
let childrenStartX = startX + lineWidth
let childrenStartY = 0
if (item.childern && item.childern.length > 0) {
childcount = getAllCount(node)
} else {
childcount = 1
}
// 当前位置的y轴
childrenStartY = start + childcount * lineHeight / 2 - (childcount - 1) * linemargin
start = start - childcount * lineHeight / 2 - linemargin
// 绘图
let type = 0
if (item.nodeType == 4) {
if (item.transformGroup == 2) {
type = 1
} else {
type = 2
}
} else if (item.nodeType == 5) {
type = 3
}
darwLine(startX, startY, childrenStartX, childrenStartY, type, item.nodeName)
render(item, childrenStartX, childrenStartY, type)
})
// 计算子元素的位置
}
}
render(obj[0], 0, 250)
})
</script>
<style scoped>
.about {
width: 100vw;
height: 100vh;
}
.cavas {
width: 600px;
height: 1000px;
margin: 0 auto;
background: rgba(0, 0, 0, 0.2);
}
</style>
2,横向树状图
<template>
<div class="main">
<div class="canvas">
<canvas ref="canvas" width="1000" height="500"></canvas>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue"
let node = {
title: "浙江省",
children: [
{
title: "杭州市",
children: [
{
title: "拱墅区",
children: [
{
title: "北部软件园",
},
],
},
{
title: "余杭区",
children: [
{
title: "梦想小镇",
},
{
title: "欧美金融城",
},
{
title: "华容科技城",
},
],
},
],
},
{
title: "宁波市",
children: [
{
title: "海曙区",
children: [
{
title: "疆良小镇",
},
{
title: "欧美金融城",
},
],
},
{
title: "江北区",
children: [
{
title: "华兴小镇",
},
{
title: "感电东方城",
},
],
},
],
},
],
};
const canvas = ref()
onMounted(() => {
const ctx = canvas.value.getContext('2d')
ctx.strokeStyle = 'red'
// 绘制一个盒子
var drawTextItem = (startX, startY, rectWidth, rectHeight,title) => {
ctx.beginPath()
ctx.rect(startX, startY, rectWidth, rectHeight)
ctx.strokeStyle = "red";
ctx.fillStyle = "#c7c2c2";
ctx.stroke()
// 写文字
ctx.fill()
ctx.fillStyle = "red";
ctx.textBaseline="middle"
let { width: textWidth } = ctx.measureText(title);
ctx.fillText(title, startX + (rectWidth - textWidth) / 2, startY + rectHeight / 2, textWidth);
}
//绘制线
var drawLine = (startX, startY, chindX, chindY) => {
let rectWidth = 100
let rectHeight = 50
let rectMargin = 12
ctx.moveTo(startX + rectWidth / 2, startY + rectHeight)
ctx.lineTo(startX + rectWidth / 2, startY + rectHeight + (rectMargin / 2))
ctx.lineTo(chindX + rectWidth / 2, chindY - rectMargin / 2)
ctx.lineTo(chindX + rectWidth / 2, chindY)
// ctx.lineTo(startX+rectWidth/2,startY+rectHeight+12)
ctx.stroke()
}
// drawTextItem(500, 10, 100, 50)
// drawLine(500, 10, 100, 72)
//
// 计算当前的框的位置
// 计算子节点的位置
// 计算当前节点下最后一组有几个节点
let getAllCount = (node)=>{
let count = 0
node.children.reduce((pre,cur)=>{
count += pre + (cur.children&&cur.children.length>0)? getAllCount(cur):1
return count
},0)
return count
}
// console.log(getAllCount(node),'count')
// 循环对象 计算当前盒子的起始位置
var render = (stratX,startY,node)=>{
// 计算起始位置
let rectWidth = 100
let rectHeight = 50
let rectMargin = 12
// 初次进入,绘制第一个框
drawTextItem(stratX,startY,rectWidth,rectHeight,node.title)
if(node.children && node.children.length>0){
// 查看当前节点下,最后一层有多少元素
let nodecount = getAllCount(node)
// 计算child 元素的 起始位置
// startX,500为第一个元素坐标
// 子元素 长度 = 个数*宽度 + (个数-1)*间距
// 子元素 中心位置 长度 / 2
// 子元素 起始位置 500 - 长度 (离中心500位置的坐标),为负数则是超过画布
let start = stratX - (nodecount * rectWidth +(nodecount-1)*rectMargin) /2
// 绘制子节点
node.children.forEach((item,index)=>{
// 获取当前子节点下有多少元素
let nodeLenth = 0
let childrenStartX = 0
// 当前子节点 y的位置,距离顶部的位置
let childrenStartY = startY+rectMargin + rectHeight
if(item.children && item.children.length>0){
nodeLenth = getAllCount(item)
}else{
// 如果没有,空一格出来(父节点有位置)
nodeLenth = 1
}
// 计算子元素的位置
childrenStartX = start + (nodeLenth * rectWidth + (nodeLenth-1)*rectMargin)/2
start = start + (nodeLenth * rectWidth + (nodeLenth-1)*rectMargin) + rectMargin
// 绘制子节点
drawLine(stratX,startY,childrenStartX,childrenStartY)
render(childrenStartX,childrenStartY,item)
})
}
}
render(500,10,node)
})
</script>
<style scoped>
.mian {
width: 100vw;
height: 100vh;
overflow: hidden;
}
.canvas {
width: 1000px;
height: 500px;
background: rgba(0, 0, 0, 0.2);
margin: 50px auto;
}
</style>