用Canvas画一棵二叉树
笔墨伺候
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// 然后便可以挥毫泼墨了
树的样子
const root = {value: 'A',label: '100',left: {value: 'B',label: '70',left: {value: 'D',label: '40',left: {value: 'H',label: '20',left: null,right: null},right: {value: 'I',label: '20',left: null,right: null}},right: {value: 'E',label: '30',left: null,right: null}},right: {value: 'C',label: '30',left: {value: 'F',label: '15',left: null,right: null},right: {value: 'G',label: '15',left: null,right: null}}}
构思构思
这样一幅大作,无非就是由黑色的正方形+线段构成
这正方形怎么画
function drawRect(text, x, y, unit) {ctx.fillRect(x, y, unit, unit)// fillRect(x, y, width, height) // x与y指定了在canvas画布上所绘制的矩形的左上角(相对于原点)的坐标// width和height设置矩形的尺寸。ctx.font = "14px serif"ctx.fillText(text, x + unit, y + unit) // 再给每个正方形加个名字
}
这直线怎么画
function drawLine(x1, y1, x2, y2) {ctx.moveTo(x1, y1)ctx.lineTo(x2, y2)ctx.stroke()
}
这关系怎么画
// 前序遍历二叉树
function preOrderTraverse(root, x, y){drawRect(root.value, x, y)if(root.left){drawLine(x, y, ...)preOrderTraverse(root.left, ...)}if(root.right){drawLine(x, y, ...)preOrderTraverse(root.right, ...)}
}
现在遇到个小问题,如何确定节点的子节的位置?
父节点与子结点在y轴上的距离固定,为正方形长度unit的两倍;父节点与子结点在x轴上的距离满足n2=(n1+2)*2-2
,其中设父节点与子结点在x轴上最短的距离n0=1
,即unit,而父节点与子结点在x轴上最长的距离取决于该树的层数。
如何得到树的深度?
function getDeepOfTree(root) {if (!root) {return 0}let left = getDeepOfTree(root.left)let right = getDeepOfTree(root.right)return (left > right) ? left + 1 : right + 1
}
这样父节点与子结点在x轴上最长的距离
let distance = 1
const deep = getDeepOfTree(root)
for (let i = 2; i < deep; i++) {distance = (distance + 2) * 2 - 2
}
// distance*unit 即为父节点与子结点在x轴上最长的距离
unit为正方形的长度,如何确定,假设canvas的宽度为1000,由深度deep可知,树的最大宽度为Math.pow(2, deep - 1)
,最底层的正方形占据4个unit。
所以unit是如此计算,const unit = 1000 / (Math.pow(2, deep - 1) * 4 + 8)
,+8是个备用空间。
代码
<html><body><canvas id="canvas" width="1000"></canvas><script>const root = {value: 'A',label: '100',left: {value: 'B',label: '70',left: {value: 'D',label: '40',left: {value: 'H',label: '20',left: null,right: null},right: {value: 'I',label: '20',left: null,right: null}},right: {value: 'E',label: '30',left: null,right: null}},right: {value: 'C',label: '30',left: {value: 'F',label: '15',left: null,right: null},right: {value: 'G',label: '15',left: null,right: null}}}const canvas = document.getElementById('canvas')const ctx = canvas.getContext('2d')const deep = getDeepOfTree(root)let distance = 1for (let i = 2; i < deep; i++) {distance = (distance + 2) * 2 - 2}const unit = 1000 / (Math.pow(2, deep - 1) * 4 + 8)canvas.setAttribute('height', deep * unit * 4)const rootX = (1000 - unit) / 2const rootY = unitpreOrderTraverse(root, rootX, rootY, distance)// 得到该树的高度function getDeepOfTree(root) {if (!root) {return 0}let left = getDeepOfTree(root.left)let right = getDeepOfTree(root.right)return (left > right) ? left + 1 : right + 1}function preOrderTraverse(root, x, y, distance) {drawRect(root.value, x, y) // 绘制节点if (root.left) {drawLeftLine(x, y + unit, distance)preOrderTraverse(root.left, x - (distance + 1) * unit, y + 3 * unit, distance / 2 - 1)}if (root.right) {drawRightLine(x + unit, y + unit, distance)preOrderTraverse(root.right, x + (distance + 1) * unit, y + 3 * unit, distance / 2 - 1)}}function drawRect(text, x, y) {ctx.fillRect(x, y, unit, unit)ctx.font = "14px serif"ctx.fillText(text, x + unit, y + unit)}function drawLeftLine (x, y, distance) {ctx.moveTo(x, y)ctx.lineTo(x - distance * unit, y + 2 * unit)ctx.stroke()}function drawRightLine (x, y, distance) {ctx.moveTo(x, y)ctx.lineTo(x + distance * unit, y + 2 * unit)ctx.stroke()}</script>
</body></html>
来点互动
实现移动至节点出现tooltip
首先要有tooltip
<div id="tooltip" style="position:absolute;"></div>
...
const tooltip = document.getElementById('tooltip')
由于canvas是一个整体元素,所以只能给canvas绑定事件,根据鼠标的坐标,判断是否落在某个正方形区域内
这里有个关健个函数
ctx.rect(0, 0, 100, 100)
ctx.isPointInPath(x, y)
// 判断x,y是否落在刚刚由path绘制出的区域内
所以在绘制正方形时还要将其path记下来
let pathArr = []
function preOrderTraverse(root, x, y, distance) {pathArr.push({x,y,value: root.value,label: root.label})// 记录正方形左上角的位置,就可以重绘路径drawRect(root.value, x, y) // 绘制节点if (root.left) {drawLeftLine(x, y + unit, distance)preOrderTraverse(root.left, x - (distance + 1) * unit, y + 3 * unit, distance / 2 - 1)}if (root.right) {drawRightLine(x + unit, y + unit, distance)preOrderTraverse(root.right, x + (distance + 1) * unit, y + 3 * unit, distance / 2 - 1)}
}
绑定事件
// 模拟鼠标hover效果
canvas.addEventListener('mousemove', (e) => {let i = 0while (i < pathArr.length) {ctx.beginPath()ctx.rect(pathArr[i].x, pathArr[i].y, unit, unit)if (ctx.isPointInPath(e.offsetX, e.offsetY)) {canvas.style.cursor = 'pointer'tooltip.innerHTML = `<span style="font-size:14px;">${pathArr[i].label}</span>`tooltip.style.top = `${pathArr[i].y + unit + 4}px`tooltip.style.left = `${pathArr[i].x + unit}px`break} else {i++}}if (i === pathArr.length) {canvas.style.cursor = 'default'tooltip.innerHTML = ``}
})
线上demo
JSBin地址
用Canvas画一棵二叉树相关推荐
- 哪两种遍历方式可以唯一确定一棵二叉树,结合力扣105题
对于一棵树的前中序三种顺序的遍历方式,任何一种单独拿出来都无法确定一棵树,那么两种遍历方式得到的节点数据能否构建一棵二叉树呢? 先来看看能有哪几种组合: 先序遍历 + 中序遍历 后序遍历 + 中序遍历 ...
- canvas 画点_css+canvas 随便画一个星空
今天躺在床上刷抖音的时候,看见了一个马克笔随便画星空的视频,很有意思. 先看效果: 开始需求分析: 1.渐变色的背景 2.画一颗树和一些草 3.水面的倒影 4.随便画点星星 5.画一颗流星 1.渐变色 ...
- 如何用 canvas 画出分形图
前言 分形是一门以非规则几何形态为研究对象的几何学,由曼德勃 罗(B.B.Mandelbrot)等人创立并命名. 分形图从整体上看,是处处不规律的.但从局部观察,图形的规则性又是相同的,即具有自相似的 ...
- 程序员的终极浪漫,用python画一棵你的专属圣诞树
turtle是python3自带的绘图模块.这个模块最有趣的地方在于它完全是拟人化的.在真正绘画开始前,你可以设置画布大小&颜色,然后指定画笔的形状(默认是小乌龟~)以及颜色,最后指定坐标,就 ...
- 樱花的季节,教大家用canvas画出飞舞的樱花树
又到了樱花的季节,教大家使用canvas画出飞舞的樱花树效果. 废话少说,先看效果. 演示效果地址:http://suohb.com/work/tree4.htm 查看演示效果 第一步,我们先画出一棵 ...
- java代码画樱花_樱花的季节,教大家用CANVAS画出飞舞的樱花树
又到了樱花的季节,教大家使用canvas画出飞舞的樱花树效果. 废话少说,先看效果. 演示效果地址:http://suohb.com/work/tree4.htm 第一步,我们先画出一棵树的主体. 我 ...
- java代码画樱花飘落_樱花的季节,教大家用canvas画出飞舞的樱花树
又到了樱花的季节,教大家使用canvas画出飞舞的樱花树效果. 废话少说,先看效果. 演示效果地址:http://suohb.com/work/tree4.htm 第一步,我们先画出一棵树的主体. 我 ...
- 三月桃花开,用python给你带来你的桃花运,详细解析画一棵表白树!
程序员就只能简简单单的用手里的代码来实现自己最直接的想法.-------------鲁迅表示这句话他没说过--------------------------------------------- 在 ...
- python椭圆花瓣_用Python给你带来你的桃花运,详细解析画一棵表白树
程序员就只能简简单单的用手里的代码来实现自己最直接的想法.-------------鲁迅表示这句话他没说过 在家上网课贼无聊,于是复习以前的知识点,瞥到之前用递归实现的科赫曲线,突然来了一点头绪,上博 ...
- 用python给你带来你的桃花运,详细解析画一棵表白树!
程序员就只能简简单单的用手里的代码来实现自己最直接的想法.-------------鲁迅表示这句话他没说过--------------------------------------------- 在 ...
最新文章
- 秘钥加密码的登录模式
- LeetCode Gas Station(双指针 )
- 桌面虚拟化“寻人行动”-转裁
- React Native debug debugger
- 攻打医院服务器的SamSam勒索木马分析
- 学习笔记~~~~~Set接口实现
- C#中Lock的秘密
- 前端学习(3246):react的生命周期getSnap
- mysql数据库管理系统模式_MYSQL命令行模式管理MySql的一点心得
- access实例_西门子PLC1200组态王跟Access数据库-⑥组态王变量
- Javascript .map文件-JavaScript源地图
- 【Android开发】jarsigner重新打包apk
- 深入浅出设计模式python_Head First Python(第2版) 中文版 高清pdf扫描版[161MB]
- 数字水印--给我的文件充当保护神
- 以赛促产 以赛引才 |第六届世界智能大会·中国华录杯数据湖算法大赛正式启动
- java基础回顾之Map中 TreeMap排序原理-二叉树
- MISC总结——隐写术(一)
- HTML+CSS系列实战之表格
- js获取当前时间并转换为一定的格式
- Python:将list写入Excel