笔墨伺候

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画一棵二叉树相关推荐

  1. 哪两种遍历方式可以唯一确定一棵二叉树,结合力扣105题

    对于一棵树的前中序三种顺序的遍历方式,任何一种单独拿出来都无法确定一棵树,那么两种遍历方式得到的节点数据能否构建一棵二叉树呢? 先来看看能有哪几种组合: 先序遍历 + 中序遍历 后序遍历 + 中序遍历 ...

  2. canvas 画点_css+canvas 随便画一个星空

    今天躺在床上刷抖音的时候,看见了一个马克笔随便画星空的视频,很有意思. 先看效果: 开始需求分析: 1.渐变色的背景 2.画一颗树和一些草 3.水面的倒影 4.随便画点星星 5.画一颗流星 1.渐变色 ...

  3. 如何用 canvas 画出分形图

    前言 分形是一门以非规则几何形态为研究对象的几何学,由曼德勃 罗(B.B.Mandelbrot)等人创立并命名. 分形图从整体上看,是处处不规律的.但从局部观察,图形的规则性又是相同的,即具有自相似的 ...

  4. 程序员的终极浪漫,用python画一棵你的专属圣诞树

    turtle是python3自带的绘图模块.这个模块最有趣的地方在于它完全是拟人化的.在真正绘画开始前,你可以设置画布大小&颜色,然后指定画笔的形状(默认是小乌龟~)以及颜色,最后指定坐标,就 ...

  5. 樱花的季节,教大家用canvas画出飞舞的樱花树

    又到了樱花的季节,教大家使用canvas画出飞舞的樱花树效果. 废话少说,先看效果. 演示效果地址:http://suohb.com/work/tree4.htm 查看演示效果 第一步,我们先画出一棵 ...

  6. java代码画樱花_樱花的季节,教大家用CANVAS画出飞舞的樱花树

    又到了樱花的季节,教大家使用canvas画出飞舞的樱花树效果. 废话少说,先看效果. 演示效果地址:http://suohb.com/work/tree4.htm 第一步,我们先画出一棵树的主体. 我 ...

  7. java代码画樱花飘落_樱花的季节,教大家用canvas画出飞舞的樱花树

    又到了樱花的季节,教大家使用canvas画出飞舞的樱花树效果. 废话少说,先看效果. 演示效果地址:http://suohb.com/work/tree4.htm 第一步,我们先画出一棵树的主体. 我 ...

  8. 三月桃花开,用python给你带来你的桃花运,详细解析画一棵表白树!

    程序员就只能简简单单的用手里的代码来实现自己最直接的想法.-------------鲁迅表示这句话他没说过--------------------------------------------- 在 ...

  9. python椭圆花瓣_用Python给你带来你的桃花运,详细解析画一棵表白树

    程序员就只能简简单单的用手里的代码来实现自己最直接的想法.-------------鲁迅表示这句话他没说过 在家上网课贼无聊,于是复习以前的知识点,瞥到之前用递归实现的科赫曲线,突然来了一点头绪,上博 ...

  10. 用python给你带来你的桃花运,详细解析画一棵表白树!

    程序员就只能简简单单的用手里的代码来实现自己最直接的想法.-------------鲁迅表示这句话他没说过--------------------------------------------- 在 ...

最新文章

  1. 秘钥加密码的登录模式
  2. LeetCode Gas Station(双指针 )
  3. 桌面虚拟化“寻人行动”-转裁
  4. React Native debug debugger
  5. 攻打医院服务器的SamSam勒索木马分析
  6. 学习笔记~~~~~Set接口实现
  7. C#中Lock的秘密
  8. 前端学习(3246):react的生命周期getSnap
  9. mysql数据库管理系统模式_MYSQL命令行模式管理MySql的一点心得
  10. access实例_西门子PLC1200组态王跟Access数据库-⑥组态王变量
  11. Javascript .map文件-JavaScript源地图
  12. 【Android开发】jarsigner重新打包apk
  13. 深入浅出设计模式python_Head First Python(第2版) 中文版 高清pdf扫描版[161MB]
  14. 数字水印--给我的文件充当保护神
  15. 以赛促产 以赛引才 |第六届世界智能大会·中国华录杯数据湖算法大赛正式启动
  16. java基础回顾之Map中 TreeMap排序原理-二叉树
  17. MISC总结——隐写术(一)
  18. HTML+CSS系列实战之表格
  19. js获取当前时间并转换为一定的格式
  20. Python:将list写入Excel

热门文章

  1. 艾美智能影库服务器ip,华语视听,家庭影院,发烧音响,智能家居,私人影院,声学装修,专业音箱-艾美影库 MS-300...
  2. java——》解析简历
  3. ccf 节日 java 思路
  4. 弗洛伊德的乌龟与兔子
  5. unity网络实战开发(丛林战争)-正式开发阶段(015-游戏场景及开始界面UI搭建)
  6. Wlan学习—无线网络安全
  7. 让Ipad买前爱奇艺买后生产力——浏览器编程之Projector运行Idea(超详细)
  8. python怎么读write_python怎么读
  9. 顺序结构-计算标准体重
  10. 移动终端基础数据管理系统