1.什麼是canvas
Canvas 是 HTML5 中新出的一個元素,開發者可以在上面繪製一系列圖形
canvas的畫布是一個矩形區域,可以控制其每一畫素
canvas 擁有多種繪製路徑、矩形、圓形、字元以及新增影像的方法
Canvas提供的功能更適合畫素處理,動態渲染和大資料量繪製
2.canvas的基本用法
2.1渲染上下文:
<canvas>
元素創造了一個固定大小的畫布,它公開了一個或多個渲染上下文,其可以用來繪製和處理要展示的內容。
<canvas id="chart1" width="600" height="600"></canvas>
const chart1 = document.getElementById("chart1")
let ctx = chart1.getContext('2d')複製程式碼
2.2 基本繪製
beginPath()
新建一條路徑,生成之後,圖形繪製命令被指向到路徑上生成路徑。
closePath()
閉合路徑之後圖形繪製命令又重新指向到上下文中。
stroke()
通過線條來繪製圖形輪廓。
fill()
通過填充路徑的內容區域生成實心的圖形。
lineTo(x, y)
繪製一條從當前位置到指定x以及y位置的直線
arc(x, y, radius, startAngle, endAngle, anticlockwise)
畫一個以(x,y)為圓心的以radius為半徑的圓弧(圓),從startAngle開始到endAngle結束,按照anticlockwise給定的方向(預設為順時針)來生成
fillStyle = color
設定圖形的填充顏色。
strokeStyle = color
設定圖形輪廓的顏色。
createLinearGradient(x1, y1, x2, y2)
createLinearGradient 方法接受 4 個引數,表示漸變的起點 (x1,y1) 與終點 (x2,y2)
// 三角
ctx.beginPath();
ctx.moveTo(10, 10);
ctx.lineTo(50, 10);
ctx.lineTo(50, 50);
ctx.lineTo(10, 10);
ctx.strokeStyle = "#b18ea6"
ctx.lineWidth = 2
ctx.stroke();
ctx.closePath()
// 矩形
ctx.fillStyle = "#00f"
ctx.fillRect(70, 10, 40 ,40)
// 扇形
ctx.beginPath();
ctx.moveTo(130, 10)
ctx.arc(130, 10, 40, Math.PI / 4, Math.PI / 2, false)
let grd = ctx.createLinearGradient(130, 10, 150, 50);
grd.addColorStop(0, "#f00")
grd.addColorStop(1, "#0ff")
ctx.fillStyle = grd
ctx.fill()複製程式碼
fillText(text, x, y [, maxWidth])
在指定的(x,y)位置填充指定的文字,繪製的最大寬度是可選的.
strokeText(text, x, y [, maxWidth])
在指定的(x,y)位置繪製文字邊框,繪製的最大寬度是可選的.
ctx.font = "32px serif";
ctx.textAlign = "center"
ctx.fillText("canvas", 100, 100);
ctx.strokeText("canvas", 100, 130);複製程式碼
2.3 canvas實踐
2.3.1 餅圖
繪製title,使用繪製文字api
ctx.font = "28px serif";
ctx.textAlign = "center"
ctx.fillText(option.title, 300, 30);複製程式碼
繪製圖例,使用繪製文字和矩形的方法
for(..data..) {
ctx.fillRect (preLegend, 50, 30, 20)
preLegend += 40 // 偏移量
ctx.font = "20px serif";
ctx.fillText(item.name, preLegend, 78);
preLegend += ctx.measureText(item.name).width + 16 // measureText返回傳入字串的渲染尺寸
}複製程式碼
使用上述繪製扇形的方法繪製餅圖中每個元素的比例
option.data.map((item, index) => {
ctx.beginPath()
ctx.moveTo(300, 300)
let start = Math.PI * 2 * preArc // preArc角度偏移量
let end = Math.PI * 2 * (preArc + item.num / total)
ctx.arc(300, 300, 200, start, end, false)
ctx.fillStyle = defaultColor[index]
ctx.fill()
preArc += item.num / total
})複製程式碼
為每個扇形元素新增名稱和數量,連線圓心和對應扇形圓弧中心並延長,繪製元素名稱和數量即可
for(..data..) {
ctx.font = "16px serif";
let lineEndx = Math.cos(start + (end - start) / 2) * 230 + 300 // 三角函式計算下座標,每個扇形圓弧中間
let lineEndy = Math.sin(start + (end - start) / 2) * 230 + 300
// 中軸右邊延線向右,左邊延線向左
let lineEndx2 = lineEndx > 300 ? lineEndx + ctx.measureText(`${item.name}: ${item.num}`).width : lineEndx - ctx.measureText(`${item.name}: ${item.num}`).width
ctx.moveTo(300, 300)
ctx.lineTo(lineEndx, lineEndy)
ctx.lineTo(lineEndx2, lineEndy)
ctx.strokeStyle = defaultColor[index]
ctx.stroke()
ctx.closePath()
ctx.fillText(`${item.name}: ${item.num}`, lineEndx2, lineEndy)
}複製程式碼
2.3.2 折線柱狀圖
繪製座標軸線,兩條垂直線段
ctx.strokeStyle = '#ccc'
ctx.beginPath()
ctx.moveTo(50, 100)
ctx.lineTo(50, 350)
ctx.lineTo(550, 350)
ctx.stroke()
ctx.closePath()複製程式碼
繪製柱狀圖,計算座標,渲染對應高度的矩形即可
option.data.map((item, index) => {
let x = 80 + index * 60
let y = 350 - item.num/total * 500 * 2
let w = 30
let h = item.num/total * 500 * 2
ctx.fillStyle = defaultColor[index]
ctx.fillRect(x,y,w,h)
})複製程式碼
繪製折線,使用畫線api連線每個矩形上邊中線即可,這裡使用了漸變效果
if(posList.length > 1) {
ctx.beginPath()
ctx.moveTo(posList[index - 1].x + w / 2, posList[index - 1].y)
ctx.lineTo(x + w / 2, y)
let grd = ctx.createLinearGradient(posList[index - 1].x + w / 2, posList[index - 1].y, x + w / 2, y);
grd.addColorStop(0,defaultColor[index - 1])
grd.addColorStop(1,defaultColor[index])
ctx.strokeStyle = grd
ctx.lineWidth = 3
ctx.stroke()
ctx.closePath()
} 複製程式碼
繪製座標軸和元素對應的座標,以每個元素柱狀的高度畫虛線至y軸,並繪製數值;元素下方繪製元素名稱
ctx.beginPath()
ctx.moveTo(x + w / 2, y)
ctx.lineTo(50, y)
ctx.setLineDash([4]) // 使用虛線
ctx.strokeStyle = defaultColor[index]
ctx.lineWidth = 0.5
ctx.stroke()
ctx.closePath()
ctx.font = "20px serif";
ctx.fillText(item.num, 35, y + 8);
ctx.font = "20px serif";
ctx.fillText(item.name, x + w / 2, 370);複製程式碼
2.3.3 水印
後臺系統中很多都有接入水印,這裡使用canvas實踐一次
rotate
方法,它用於以原點為中心旋轉 canvas。
rotate(angle)
這個方法只接受一個引數:旋轉的角度(angle),它是順時針方向的,以弧度為單位的值。
首先繪製一個水印模板
let watermarkItem = document.createElement("canvas")
watermarkItem.id = "watermark-item"
watermarkItem.width = 160
watermarkItem.height = 160
document.body.appendChild(watermarkItem)
let ctxitem = watermarkItem.getContext('2d')
ctxitem.font = "28px serif"
ctxitem.textAlign = "center"
ctxitem.save()
ctxitem.rotate(-Math.PI/4)
ctxitem.fillText("canvas", 0, 110)
ctxitem.restore()
複製程式碼
以該模板渲染整個瀏覽器視窗即可
createPattern()
方法在指定的方向內重複指定的元素。
元素可以是圖片、視訊,或者其他 <canvas> 元素。
被重複的元素可用於繪製/填充矩形、圓形或線條等等。
let pat = ctx.createPattern(item, "repeat")
ctx.fillStyle = pat
ctx.fillRect(0, 0, watermark.width, watermark.height)複製程式碼
接下來隱藏第一個模板,並監聽瀏覽器視窗的變化,視窗變化時重新渲染水印覆蓋視窗
window.onload = createWatermark
window.onresize = drawWatermark
function createWatermark() {
let watermarkItem = document.createElement("canvas")
let watermark = document.createElement("canvas")
watermarkItem.id = "watermark-item"
watermarkItem.width = 160
watermarkItem.height = 160
watermarkItem.style.display = "none"
watermark.id = "watermark"
watermark.width = window.innerWidth
watermark.height = window.innerHeight
document.body.appendChild(watermark)
document.body.appendChild(watermarkItem)
drawWatermark()
}
function drawWatermark () {
let item = document.getElementById("watermark-item")
let watermark = document.getElementById("watermark")
watermark.id = "watermark"
watermark.width = window.innerWidth
watermark.height = window.innerHeight
let ctxitem = item.getContext('2d')
let ctx = watermark.getContext('2d')
ctxitem.font = "28px serif"
ctxitem.textAlign = "center"
ctxitem.save()
ctxitem.rotate(-Math.PI/4)
ctxitem.fillText("canvas", 0, 110)
ctxitem.restore()
let pat = ctx.createPattern(item, "repeat")
ctx.fillStyle = pat
ctx.fillRect(0, 0, watermark.width, watermark.height)
}複製程式碼
3.3.4 樹的應用
這裡使用canvas繪製一棵樹,並具備拖拽、增刪改等功能
首先遍歷樹的節點,繪製樹的節點
這裡對每層節點都設定不同的高度;對每個節點計算應占有的x軸空間,子節點平分父節點所佔空間
遞迴遍歷中
datalist.map((item, index) => {
if(p.children) {
// x1,x2為佔用x軸空間座標
item.x1 = p.x1 + (p.x2 - p.x1) * index / p.children.length
item.x2 = p.x1 + (p.x2 - p.x1) * (index + 1) / p.children.length
item.x = item.x1 + (item.x2 - item.x1) / 2
item.y = item.level * 100
} else {
item.x = 375
item.y = item.level * 100
item.x1 = 0
item.x2 = 800
}
ctx.fillStyle = defaultColor[item.level]
ctx.fillRect(item.x, item.y, 50, 25)
}複製程式碼
然後連線子節點與父節點之間,並填寫每個節點的資訊
ctx.fillStyle = "#000"
ctx.font = "20px serif";
ctx.textAlign = "center"
ctx.fillText(`${item.name}${item.num}`, ix + 25, iy + 20);
ctx.beginPath()
ctx.moveTo(item.x + 25, item.y)
ctx.lineTo(p.x + 25, p.y + 25)
ctx.stroke()
ctx.closePath()複製程式碼
接下來完成拖拽功能,首先繫結事件,並獲得mousedown事件點選在canvas畫板上的座標,判斷是否點選座標在樹的節點上,點選的是哪個節點
如果點選在節點上,則監聽mousemove事件,讓節點跟隨滑鼠移動(通過計算新的節點座標,並重繪)
監聽到mouseup事件,即代表拖拽事件結束,清除掉mouseover事件即可
let move = (e) => {
ctx.clearRect(0,0,800,1000)
this.drawTree(this.datalist, {}, ctx, {
id,
curX: e.clientX - chart3.getBoundingClientRect().left,
curY: e.clientY - chart3.getBoundingClientRect().top
})
}
chart3.addEventListener('mousedown', (e) => {
// 計算點選在canvas上的座標
curX = e.clientX - chart3.getBoundingClientRect().left
curY = e.clientY - chart3.getBoundingClientRect().top
this.rectList.forEach(item => {
// 判斷點選在矩形上
if(curX >= item.x1 && curX <= item.x2 && curY >= item.y1 && curY <= item.y2) {
id = item.id
this.current = item
chart3.addEventListener('mousemove', move, false)
}
})
}, false)
chart3.addEventListener('mouseup', (e) => {
chart3.removeEventListener('mousemove', move)
}, false)
// 重繪節點
if(item.curX) {
ctx.fillRect(item.curX, item.curY, 50, 25)
} else {
ctx.fillRect(item.x, item.y, 50, 25)
}
ctx.fillStyle = "#000"
ctx.font = "20px serif";
ctx.textAlign = "center"
let ix = item.curX ? item.curX : item.x
let iy = item.curY ? item.curY : item.y
let px = p.curX ? p.curX : p.x
let py = p.curY ? p.curY : p.y
ctx.fillText(`${item.name}:${item.num}`, ix + 25, iy + 20);
// 重新連線
ctx.beginPath()
ctx.moveTo(ix + 25, iy)
ctx.lineTo(px + 25, py + 25)複製程式碼
接下來為節點新增增刪改的功能,右鍵點選節點時彈出操作選單
// 清除預設右鍵事件
document.oncontextmenu = function(e){
e.preventDefault();
};
chart3.addEventListener('mousedown', (e) => {
。。。。判斷點選在某個節點上
if(e.button ==2){ // 右鍵點選
this.outerVisible = true // 彈出選單
return
}
})複製程式碼
這裡使用element簡單做一個表單
刪除操作:
在遍歷節點的時候,判斷節點的id和需要刪除的節點的id一致時,將該節點置空,並重新渲染即可
修改操作:
在遍歷節點的時候,判斷節點的id和需要修改的節點的id一致時,將該節點的資料設定為新的,並重新渲染
新增節點:
在遍歷節點的時候,判斷節點的id和需要新增子節點的id一致時,在該節點樹children中新增對應的資料,並重新渲染
// 修改節點
edit(datalist) {
datalist.map((item, index) => {
if(item.id == this.current.id) {
item.num = this.num
item.name = this.name
const chart3 = document.getElementById("chart3")
let ctx = chart3.getContext('2d')
// 重新渲染
ctx.clearRect(0,0,800,1000)
this.drawTree(this.datalist, {}, ctx)
return
}
if(item.children) {
this.edit(item.children)
}
})
}複製程式碼
原始碼
<template>
<div class="hello">
<canvas id="chart1" width="600" height="600"></canvas>
<canvas id="chart2" width="600" height="600"></canvas>
<canvas id="chart3" width="800" height="1000"></canvas>
<el-dialog title="操作" :visible.sync="outerVisible">
<el-button-group>
<div>id: {{current.id}}</div>
<el-button type="primary" @click="oprNode(1)">增加節點</el-button>
<el-button type="primary" @click="deleteNode(datalist, current.id)">刪除節點</el-button>
<el-button type="primary" @click="oprNode(2)">修改節點</el-button>
</el-button-group>
<el-dialog
width="30%"
title=""
:visible.sync="innerVisible"
append-to-body>
<el-form ref="form" label-width="80px">
<el-form-item label="id">
<el-input v-model="id"></el-input>
</el-form-item>
<el-form-item label="name">
<el-input v-model="name"></el-input>
</el-form-item>
<el-form-item label="num">
<el-input v-model="num"></el-input>
</el-form-item>
<el-button @click="submit">提交</el-button>
</el-form>
</el-dialog>
</el-dialog>
</div>
</template>
<script>
// import * as tf from '@tensorflow/tfjs';
export default {
name: 'Tensor',
data() {
return {
rectList: [],
outerVisible: false,
innerVisible: false,
current: {
id: ''
},
type: '',
id: '',
num: '',
name: '',
datalist: [{
name: "A",
num: 400,
level: 1,
id: 1,
children: [
{
name: "B",
num: 300,
level: 2,
id: 2,
children: [
{
name: "D",
num: 150,
level: 3,
id: 3,
children: [
{
name: "I",
num: 70,
level: 4,
id: 4,
},
{
name: "J",
num: 80,
level: 4,
id: 5,
},
]
},
{
name: "E",
num: 50,
level: 3,
id: 6,
},
{
name: "F",
num: 100,
level: 3,
id: 7,
},
]
},
{
name: "C",
num: 100,
level: 2,
id: 8,
children: [
{
name: "G",
num: 20,
level: 3,
id: 9
},
{
name: "H",
num: 80,
level: 3,
id: 10
},
]
},
]
}]
}
},
mounted() {
this.init()
this.initTree()
document.oncontextmenu = function(e){
e.preventDefault();
};
},
methods: {
init() {
// const model = tf.sequential();
// model.add(tf.layers.dense({units: 1, inputShape: [1]}));
// model.compile({loss: 'meanSquaredError', optimizer: 'sgd'});
// const xs = tf.tensor2d([1, 2, 3, 4], [4, 1]);
// const ys = tf.tensor2d([1, 3, 5, 7], [4, 1]);
// model.fit(xs, ys).then(() => {
// model.predict(tf.tensor2d([5], [1, 1])).print();
// });
let data = [{
name: 'A',
num: 80,
},{
name: 'B',
num: 60,
},{
name: 'C',
num: 20,
},{
name: 'D',
num: 40,
},{
name: 'E',
num: 30,
},{
name: 'F',
num: 70,
},{
name: 'G',
num: 60,
},{
name: 'H',
num: 40,
}]
const chart1 = document.getElementById("chart1")
const chart2 = document.getElementById("chart2")
this.createChart(chart1, {
title: "canvas餅圖:各階段比例",
type: "pie",
data: data
})
this.createChart(chart2, {
title: "canvas折線柱狀圖",
type: "bar",
data: data
})
},
createChart(canvas, option) {
const defaultColor = ["#ff8080", "#b6ffea", "#fb0091", "#fddede", "#a0855b", "#b18ea6", "#ffc5a1", "#08ffc8"]
let ctx = canvas.getContext('2d')
ctx.font = "28px serif";
ctx.textAlign = "center"
ctx.fillText(option.title, 300, 30);
let total = option.data.reduce((pre, a) => {
return pre + a.num
}, 0)
let preArc = 0
let preLegend = 40
// 餅圖
if(option.type === "pie") {
ctx.arc(300,300,210,0,Math.PI*2,false)
ctx.strokeStyle = '#ccc'
ctx.stroke()
ctx.closePath()
option.data.map((item, index) => {
ctx.beginPath()
ctx.moveTo(300, 300)
let start = Math.PI * 2 * preArc
let end = Math.PI * 2 * (preArc + item.num / total)
ctx.arc(300, 300, 200, start, end, false)
ctx.fillStyle = defaultColor[index]
ctx.fill()
ctx.font = "16px serif";
let lineEndx = Math.cos(start + (end - start) / 2) * 230 + 300
let lineEndy = Math.sin(start + (end - start) / 2) * 230 + 300
let lineEndx2 = lineEndx > 300 ? lineEndx + ctx.measureText(`${item.name}: ${item.num}`).width : lineEndx - ctx.measureText(`${item.name}: ${item.num}`).width
ctx.moveTo(300, 300)
ctx.lineTo(lineEndx, lineEndy)
ctx.lineTo(lineEndx2, lineEndy)
ctx.strokeStyle = defaultColor[index]
ctx.stroke()
ctx.closePath()
ctx.fillText(`${item.name}: ${item.num}`, lineEndx2, lineEndy);
// 圖例
ctx.fillRect (preLegend, 60, 30, 20)
preLegend += 40
ctx.font = "20px serif";
ctx.fillText(item.name, preLegend, 78);
preLegend += ctx.measureText(item.name).width + 16
preArc += item.num / total
})
} else if(option.type === "bar"){
ctx.strokeStyle = '#ccc'
ctx.beginPath()
ctx.moveTo(50, 100)
ctx.lineTo(50, 350)
ctx.lineTo(550, 350)
ctx.stroke()
ctx.closePath()
let posList = []
preArc = 0
preLegend = 40
option.data.map((item, index) => {
let x = 80 + index * 60
let y = 350 - item.num/total * 500 * 2
let w = 30
let h = item.num/total * 500 * 2
ctx.fillStyle = defaultColor[index]
ctx.fillRect(x,y,w,h)
// 座標軸
ctx.beginPath()
ctx.moveTo(x + w / 2, y)
ctx.lineTo(50, y)
ctx.setLineDash([4])
ctx.strokeStyle = defaultColor[index]
ctx.lineWidth = 0.5
ctx.stroke()
ctx.closePath()
ctx.font = "20px serif";
ctx.fillText(item.num, 35, y + 8);
ctx.font = "20px serif";
ctx.fillText(item.name, x + w / 2, 370);
// 圖例
ctx.fillRect (preLegend, 60, 30, 20)
preLegend += 40
ctx.font = "20px serif";
ctx.fillText(item.name, preLegend, 78);
preLegend += ctx.measureText(item.name).width + 16
preArc += item.num / total
posList.push({x, y})
// 折線
if(posList.length > 1) {
ctx.beginPath()
ctx.moveTo(posList[index - 1].x + w / 2, posList[index - 1].y)
ctx.lineTo(x + w / 2, y)
let grd = ctx.createLinearGradient(posList[index - 1].x + w / 2, posList[index - 1].y, x + w / 2, y);
grd.addColorStop(0,defaultColor[index - 1])
grd.addColorStop(1,defaultColor[index])
ctx.strokeStyle = grd
ctx.lineWidth = 3
ctx.setLineDash([])
ctx.lineCap = "round"
ctx.lineJoin = "round"
ctx.stroke()
ctx.closePath()
}
})
}
},
oprNode(type) {
this.outerVisible = false
this.innerVisible = true
this.type = type
},
submit() {
if(this.type == 1) {
this.add(this.datalist)
} else {
this.edit(this.datalist)
}
},
add(datalist) {
datalist.map((item, index) => {
if(item.id == this.current.id) {
if(!item.children) {
item.children = [{
num: this.num,
id: this.id,
name: this.name,
level: item.level + 1
}]
} else {
item.children.push({
num: this.num,
id: this.id,
name: this.name,
level: item.level + 1
})
}
const chart3 = document.getElementById("chart3")
let ctx = chart3.getContext('2d')
ctx.clearRect(0,0,800,1000)
this.drawTree(this.datalist, {}, ctx)
return
}
if(item.children) {
this.add(item.children)
}
})
},
edit(datalist) {
datalist.map((item, index) => {
if(item.id == this.current.id) {
item.num = this.num
item.id = this.id
item.name = this.name
const chart3 = document.getElementById("chart3")
let ctx = chart3.getContext('2d')
ctx.clearRect(0,0,800,1000)
this.drawTree(this.datalist, {}, ctx)
return
}
if(item.children) {
this.edit(item.children)
}
})
},
initTree() {
const chart3 = document.getElementById("chart3")
let ctx = chart3.getContext('2d')
let id = ''
let curX
let curY
let move = (e) => {
ctx.clearRect(0,0,800,1000)
this.drawTree(this.datalist, {}, ctx, {
id,
curX: e.clientX - chart3.getBoundingClientRect().left,
curY: e.clientY - chart3.getBoundingClientRect().top
})
}
chart3.addEventListener('mousedown', (e) => {
curX = e.clientX - chart3.getBoundingClientRect().left
curY = e.clientY - chart3.getBoundingClientRect().top
this.rectList.forEach(item => {
// 點選在矩形上
if(curX >= item.x1 && curX <= item.x2 && curY >= item.y1 && curY <= item.y2) {
id = item.id
this.current = item
if(e.button ==2){
this.outerVisible = true
return
}
chart3.addEventListener('mousemove', move, false)
}
})
}, false)
chart3.addEventListener('mouseup', (e) => {
chart3.removeEventListener('mousemove', move)
}, false)
this.drawTree(this.datalist, {}, ctx)
},
deleteNode(data, id) {
console.log('data',data)
this.outerVisible = false
if(id == 1) return
let flag = 0
data.map((item, index) => {
if(item.children) item.children.map((t, index) => {
if(t.id == id) {
flag = 1
item.children.splice(index ,1)
const chart3 = document.getElementById("chart3")
let ctx = chart3.getContext('2d')
ctx.clearRect(0,0,800,1000)
this.drawTree(this.datalist, {}, ctx)
return
}
})
if(item.children && flag == 0) {
this.deleteNode(item.children, id)
}
})
},
drawTree(datalist, p, ctx, move) {
console.log('datalist',datalist)
const defaultColor = ["#ff8080", "#b6ffea", "#fb0091", "#fddede", "#a0855b", "#b18ea6", "#ffc5a1", "#08ffc8"]
if(!p.x) this.rectList = []
datalist.map((item, index) => {
if(move && move.id == item.id) {
item.curX = move.curX
item.curY = move.curY
}
if(p.children) {
item.x1 = p.x1 + (p.x2 - p.x1) * index / p.children.length
item.x2 = p.x1 + (p.x2 - p.x1) * (index + 1) / p.children.length
item.x = item.x1 + (item.x2 - item.x1) / 2
item.y = item.level * 100
} else {
item.x = 375
item.y = item.level * 100
item.x1 = 0
item.x2 = 800
}
ctx.fillStyle = defaultColor[item.level]
if(item.curX) {
ctx.fillRect(item.curX, item.curY, 50, 25)
} else {
ctx.fillRect(item.x, item.y, 50, 25)
}
ctx.fillStyle = "#000"
ctx.font = "20px serif";
ctx.textAlign = "center"
let ix = item.curX ? item.curX : item.x
let iy = item.curY ? item.curY : item.y
let px = p.curX ? p.curX : p.x
let py = p.curY ? p.curY : p.y
ctx.fillText(`${item.name}:${item.num}`, ix + 25, iy + 20);
ctx.beginPath()
ctx.moveTo(ix + 25, iy)
ctx.lineTo(px + 25, py + 25)
ctx.stroke()
ctx.closePath()
if(item.children) {
this.drawTree(item.children, item, ctx, move)
}
let obj = {
id: item.id,
x1: item.x,
x2: item.x + 50,
y1: item.y,
y2: item.y + 25
}
if(item.curX) {
obj.x1 = item.curX
obj.x2 = item.curX + 50
obj.y1 = item.curY
obj.y2 = item.curY + 25
}
this.rectList.push(obj)
})
}
}
}
</script>
<style scoped>
#chart1 {
border: 1px solid #eee;
}
#chart2 {
border: 1px solid #eee;
}
#chart3 {
border: 1px solid #eee;
}
</style>
複製程式碼