资讯专栏INFORMATION COLUMN

用Canvas画一棵二叉树

lordharrd / 2223人阅读

摘要:如何得到树的深度这样父节点与子结点在轴上最长的距离即为父节点与子结点在轴上最长的距离为正方形的长度,如何确定,假设的宽度为,由深度可知,树的最大宽度为,最底层的正方形占据个。

笔墨伺候
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是个备用空间。

代码



  
  


来点互动

实现移动至节点出现tooltip

首先要有tooltip

... 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 = 0
  while (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 = `${pathArr[i].label}`
      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地址

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/92087.html

相关文章

  • 数据结构:二叉树

    摘要:链式存储结构由于二叉树的每个结点最多有两个孩子,所以为每个结点设计一个数据域和两个指针域。最终能得到二叉树的完整结构。这篇文章主要介绍树结构中的一种特殊存在——二叉树。主要内容有: 二叉树的概念 二叉树的基本结构 二叉树的操作 概念 二叉树: 每个结点最多有两个子结点,两个子结点是有次序的,且子结点次序不能颠倒。两个子结点一般称之为左结点及右结点。 层次: 在树中,节点的层次从...

    Ashin 评论0 收藏0
  • 使JavaScript完成二叉树的一些基本操作

    摘要:另外,由于篇幅有限,本篇的重点在于二叉树的常见算法以及实现。常见的二叉树实现代码之前写过相关的文章,是关于如何创建及遍历二叉树的,这里不再赘述。同时我们注意到,在二叉树深度比较大的时候,我们光是比较左右是不够的。 本篇为复习过程中遇到过的总结,同时也给准备面试的同学一份参考。另外,由于篇幅有限,本篇的重点在于二叉树的常见算法以及实现。 常见的二叉树实现代码 之前写过相关的文章,是关于如...

    YPHP 评论0 收藏0
  • js数据结构-二叉树二叉堆)

    摘要:二叉树二叉树是一种树形结构,它的特点是每个节点最多只有两个分支节点,一棵二叉树通常由根节点,分支节点,叶子节点组成。 二叉树 二叉树(Binary Tree)是一种树形结构,它的特点是每个节点最多只有两个分支节点,一棵二叉树通常由根节点,分支节点,叶子节点组成。而每个分支节点也常常被称作为一棵子树。 showImg(https://segmentfault.com/img/bVbmEd...

    ningwang 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<