草庐IT

d3绘制树形图, 初始化居中,可拖拽,点击某个节点也会以这个节点居中

littlesunn 2023-03-28 原文
1656706432578.gif

svg 绘制树形图, 功能点有:
1.初始化居中
2.可拖拽
3.点击某个节点也会以这个节点居中
4.悬浮某个节点出现tooltip
5.path动画描边

当你学了svg和d3的一些基础之后再来看这个案例,其中包含的知识点非常多,如果能全部掌握,相信你对d3的了解更上一层楼啦
当然你可以不学,直接复制粘贴,也是可以用的~

npm i d3 --save

<template>
  <div id="tree" :class="['tree-container']">
  </div>
</template>
<script>
import * as d3 from "d3"
const curTranslate = {
  x: 0,
  y: 0
}
export default {
  data() {
    return {
      dataset: {
        cname: "主节点",
        children: [{
          cname: "子节点1",
          children: [{
            cname: "孙子点1"
          }, {
            cname: "孙子点2"
          }]
        }, {
          cname: "子节点2",
        }, {
          cname: "子节点3",
        }],
      },
      gsize: null,
    }
  },
  mounted() {
    this.initTree();
  },
  methods: {
    initTree() {
      var timer;
      let treeContainer = document.getElementById("tree");
      var width = treeContainer.clientWidth;
      var height = treeContainer.clientHeight;
      var preTranslate = [0, 0];
      var svg = d3
        .select("#tree")
        .append("svg")
        .attr("width", width + "px")
        .attr("height", height + "px")

      var treeGroup = svg
        .append("g")
        .attr("transition", ".2s")
      let zoom = d3.zoom() // 拖拽与缩放
        .scaleExtent([.1, 10])
        .on("zoom", zoomed);
      svg.call(zoom);

      function zoomed(event) {
        let transofrm = d3.zoomTransform(this)
        treeGroup.attr("transform", transofrm.translate(curTranslate.x, curTranslate.y));
      }
      var hierarchyData = d3.hierarchy(this.dataset).sum(function (d) {
        return d.value;
      });

      var tree = d3
        .tree()
        .nodeSize([width / 6, height / 6.5])  // 以node为参考缩放,size ()以容器大小缩放,node节点过多会叠加
        .separation(function (a, b) { //定义邻居节点的距离
          return 1
        });
      var treeData = tree(hierarchyData); //获取布 局数据
      var toolsWrap = svg.append("g").attr("id", 'toolsWrap').attr("style", "display: none;position:relative");

      toolsWrap.append('rect').attr('width', 50).attr('height', 15).attr('fill', "#000").on("click", function () {
        toolsWrap.attr("transform", `translate(0, 0)`).attr("style", "display: none;")
      })
      toolsWrap.append('rect').attr('width', 50).attr('height', 15).attr('fill', "red").attr("x", 50)
      toolsWrap.append('rect').attr('width', 50).attr('height', 15).attr('fill', "green").attr("x", 100)

      var nodes = treeData.descendants(); // 节点数据
      var links = treeData.links(); //连线数据
      treeGroup.append("g")
        .selectAll("path") // 创建节点之间的连线
        .data(links)
        .enter()
        .append("path")
        .attr("d", function (d) {
          var start = { x: d.source.y, y: d.source.x };
          var end = { x: d.target.y, y: d.target.x };
          return `M${start.y},${start.x}L${start.y},${end.x - 30}L${end.y},${end.x - 30}L${end.y},${end.x}`
        })
        .attr("fill", "none")
        .attr("stroke", "#999")
        .attr("stroke-width", 2)
        .attr("stroke-dasharray", function (d) {
          return this.getTotalLength()
        })
        .attr("stroke-dashoffset", function (d) {
          return this.getTotalLength()
        })
        .transition()
        .duration(1500)
        .attr("stroke-dashoffset", 0).delay(function (d) {
          return d.source.depth * 800
        });

      var gs = treeGroup.append("g") //生成每个节点
        .selectAll("g")
        .data(nodes)
        .enter()
        .append("g")
        .attr("style", "cursor: pointer;")
        .attr("transform", function (d) {
          var cx = d.x;
          var cy = d.y;
          return `translate(${cx},${cy})`;
        })
        .on("click", function (e) {
          let point = this.getBoundingClientRect(); // 被点击节点的dom信息
          let gsize = treeGroup.node().getBoundingClientRect(); // 整个group的信息
          preTranslate = treeGroup.attr('transform').match(/translate\(.*\)/g)[0].replace("translate(", "").replace(")", "").split(".");

          d3.zoomTransform(this).x = (gsize.x + width / 2 - (point.x + point.width / 2) + gsize.width / 2)
          d3.zoomTransform(this).y = (gsize.y + height / 2 - point.y)
          curTranslate.x = 0
          curTranslate.y = 0

          let curScale = d3.zoomTransform(this).k;
          treeGroup
            .transition()
            .attr('transform', `translate(${gsize.x + width / 2 - (point.x + point.width / 2) + gsize.width / 2}, ${gsize.y + height / 2 - point.y}) scale(${curScale})`) // 将被点击的节点居中显示
        })
        .on("mouseenter", function (d, level) {
          if (timer) {
            clearTimeout(timer)
          }
          let curScale = d3.zoomTransform(svg).k || 0;
          toolsWrap.attr("transform", `translate(0,0) scale(${curScale})`).attr("style", "display: block;")
          this.appendChild(toolsWrap.node())
          let bbox = this.getBBox(); // bbox 不会因为放大而宽高变化
          console.log(bbox);
          toolsWrap.attr("transform", `translate(${bbox.x}, ${bbox.height - 4}) scale(${curScale})`).attr("style", "display: block;")
        })
        .on("mousemove", function (d, v) {
          // let point = this.getBoundingClientRect();
        }).on("mouseleave", function () {
          timer = setTimeout(() => {
            toolsWrap.attr("transform", `translate(0, 0)`).attr("style", "display: none;")
          }, 1000)
        })

      gs.append("circle") // 创建节点的圆点
        .attr("r", 4)
        .attr("fill", "#bae637")
        .attr("stroke", "#000")
        .attr("stroke-width", 1)

      gs.append("text") // 创建节点对应的文本内容
        .attr("y", 20)
        .attr("dy", 10)
        .attr("fill", function (d) {
          return "orange"
        })
        .attr("font-size", 18)
        .attr("style", (d) => {
          return !d.data.mainNode ? `transform : scale(${.8})` : ""
        })
        .text(function (d) {
          return d.data.cname;
        }).attr("x", function (d) {
          let bbox = this.getBBox(); // 居中对齐
          return -bbox.width / 2
        }).each(function (d) {
          d3.select(this.parentNode)

            .insert("rect")
            .attr("width", this.getBBox().width + 20)
            .attr("height", this.getBBox().height + 5)
            .attr("fill", d => {
              return "#000"
            })
            .attr("stroke", d => {
              return "#333"
            })
            .attr("x", function (d) {
              let bbox = this.getBBox(); //居中对齐
              return -bbox.width / 2
            })
            .attr("y", 9).each(function (d) {
              this.parentNode.insertBefore(this, this.parentNode.children[0])
            })
            .attr("style", !d.data.mainNode ? `transform: scale(${.8})` : "")
        })
      let gsize = treeGroup.node().getBoundingClientRect();
      curTranslate.x = width / 2;
      curTranslate.y = height / 2 - gsize.height / 2;
      treeGroup.attr("transform", `translate(${curTranslate.x}, ${curTranslate.y})`)
    },
    update() {  // 重绘
      d3.selectAll("svg").remove()
      this.initTree();
    },
  },
}
</script>
<style scoped lang="scss" >
.tree-container {
  position: relative;
  width: 800px;
  height: 600px;
  user-select: none;
  border: 1px solid;
  background: linear- gradient(to top, transparent 12px, #d9d9d9 12px),
    linear-gradient(to left, transparent 12px, #d9d9d9 13px);
  background-size: 13px 13px;
}
</style>

有关d3绘制树形图, 初始化居中,可拖拽,点击某个节点也会以这个节点居中的更多相关文章

  1. ruby-on-rails - 未初始化的常量 Psych::Syck (NameError) - 2

    在我的gem中,我需要yaml并且在我的本地计算机上运行良好。但是在将我的gem推送到ruby​​gems.org之后,当我尝试使用我的gem时,我收到一条错误消息=>"uninitializedconstantPsych::Syck(NameError)"谁能帮我解决这个问题?附言RubyVersion=>ruby1.9.2,GemVersion=>1.6.2,Bundlerversion=>1.0.15 最佳答案 经过几个小时的研究,我发现=>“YAML使用未维护的Syck库,而Psych使用现代的LibYAML”因此,为了解决

  2. ruby-on-rails - 未在 Ruby 中初始化的对象 - 2

    我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调

  3. ruby-on-rails - ActionController::RoutingError: 未初始化常量 Api::V1::ApiController - 2

    我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc

  4. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  5. ruby - 为什么当我调用类的实例方法时,初始化不显示为方法? - 2

    我正在写一篇关于在Ruby中几乎一切都是对象的博客文章,我试图通过以下示例来展示这一点:classCoolBeansattr_accessor:beansdefinitialize@bean=[]enddefcount_beans@beans.countendend所以从类中我们可以看出它有4个方法(当然,除非我错了):它可以在创建新实例时初始化一个默认的空bean数组它可以计算它有多少个bean它可以读取它有多少个bean(通过attr_accessor)它可以向空数组写入(或添加)更多bean(也通过attr_accessor)但是,当我询问类本身它有哪些实例方法时,我没有看到默认

  6. ruby-on-rails - 为什么在 Rails 5.1.1 中删除了 session 存储初始化程序 - 2

    我去了这个website查看Rails5.0.0和Rails5.1.1之间的区别为什么5.1.1不再包含:config/initializers/session_store.rb?谢谢 最佳答案 这是删除它的提交:Setupdefaultsessionstoreinternally,nolongerthroughanapplicationinitializer总而言之,新应用没有该初始化器,session存储默认设置为cookie存储。即与在该初始值设定项的生成版本中指定的值相同。 关于

  7. ruby-on-rails - NameError(未初始化常量 Unzipper::Zip)但仅在 Heroku 部署(Rails)上 - 2

    我有一个类unzipper.rb,它使用Rubyzip解压文件。在我的本地环境中,我可以成功解压缩文件,而无需使用require'zip'明确包含依赖项但是在Heroku上,我得到一个NameError(uninitializedconstantUnzipper::Zip)我只能通过使用明确的require来解决问题:为什么这在H​​eroku环境中是必需的,但在本地主机上却不是?我的印象是Rails自动需要所有gem。app/services/unzipper.rbrequire'zip'#OnlyrequiredforHeroku.Workslocallywithout!class

  8. ruby - 安装 gem 时未初始化的常量 Psych::Syck - 2

    我将gem推送到ruby​​gems.org,当我执行“geminstall(gem)”时出现此错误:ERROR:Whileexecutinggem...(NameError)uninitializedconstantPsych::Syck我可以执行“gembuild(gem).gemspec”来生成本地gem,然后geminstall(gem).gem并且安装正常。我还可以将gem放入我的Rails应用程序的Gemfile中,并带有指向Github存储库的指针,这也可以。我试过在多台计算机上安装gem(来自ruby​​gems.org,它们都遇到相同的错误。我不知道是什么原因导致从r

  9. ruby - 有没有一种 Ruby 方法可以删除初始化程序中的样板代码? - 2

    我写了很多initialize代码,将attrs设置为参数,类似于:classSiteClientattr_reader:login,:password,:domaindefinitialize(login,password,domain='somedefaultsite.com')@login=login@password=password@domain=domainendend有没有更像Ruby的方式来做到这一点?我觉得我在一遍又一遍地编写相同的样板设置代码。 最佳答案 您可以使用rubyStruct:classMyClass或

  10. ruby-on-rails - Spring 不起作用。 [未初始化常量 Spring::SID::DL] - 2

    我无法运行Spring。这是错误日志。myid-no-MacBook-Pro:myid$spring/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/lib/spring/sid.rb:17:in`fiddle_func':uninitializedconstantSpring::SID::DL(NameError)from/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/li

随机推荐