首页 > 解决方案 > D3 Collapse Tree 渲染数千个节点

问题描述

我正在使用 D3 来渲染包含数千个节点的可折叠树。第一个深度只有大约 20 个节点;扩展这些节点后会出现问题。深度 2 中的一些节点包含数百个子节点,因此它们开始消失。我似乎无法弄清楚如何解决这个问题。一般来说,我对使用 D3 和 SVG 还是很陌生,任何资源都会有所帮助。

我的问题是:

  1. 如何使包裹我的树的 svg 更加动态,以便它随着我的树的扩展而扩展?
  2. 我的树在初始加载时的位置非常低,我怎样才能将它居中?

任何调整树的位置以适应许多节点的建议将不胜感激。

我的 JSFiddle

onLoad()

async function onLoad() {
  const width = 960
  const height = document.body.clientHeight
  const dx = 10
  const dy = 156

  const metrics = getData()
  const root = d3.hierarchy(metrics)
  console.log(root)

  const margin = {
    top: 10,
    right: 120,
    bottom: 10,
    left: 40
  }
  const diagonal = d3.linkHorizontal().x(d => d.y).y(d => d.x)

  root.x0 = dy / 2
  root.y0 = 0

  root.descendants().forEach((d, i) => {
    d.id = i
    d._children = d.children
    // collapse(d)
    if (d.depth && d.depth <= 1) d.children = null
  })

  const svg = d3.select('svg')
    // .attr('viewbox', [-margin.left, -margin.top, width, dx])
    .style('user-select', 'none')

  const gNode = svg.append('g')
    .attr('cursor', 'pointer')
    .attr('pointer-events', ' all')
    .attr('width', width)
    .attr('height', height)

  const tree = d3.tree().nodeSize([dx, dy])

  function update(source) {
    const duration = d3.event && d3.eventaltKey ? 2500 : 250
    const nodes = root.descendants().reverse()
    const links = root.links()
    console.log('updating tree...')

    tree(root) // compute new tree

    let left = root
    let right = root
    root.eachBefore(n => {
      if (n.x < left.x) left = n
      if (n.x > right.x) right = n
    })

    const height = right.x - left.x + margin.top + margin.bottom
    console.log(left.x)
    const transition = svg.transition()
      .duration(duration)
      .attr('viewBox', [-margin.left, left.x - margin.top, width / 2, height / 2])
      .tween('resize', window.ResizeObserver ? null : () => () => svg.dispatch('toggle'))

    // Update the nodes…
    const node = gNode.selectAll('g')
      .data(nodes, d => d.id)

    // Enter any new nodes at the parent's previous position.
    const nodeEnter = node.enter().append('g')
      .attr('transform', d => `translate(${source.y0},${source.x0})`)
      .attr('fill-opacity', 0)
      .attr('stroke-opacity', 0)
      .on('click', d => {
        d.children = d.children ? null : d._children
        update(d)
      })

    nodeEnter.append('circle')
      .attr('r', d => d._children ? 2.5 : 1)
      .attr('fill', d => d._children ? '#555' : '#999')
      .attr('stroke-width', 2)

    nodeEnter.append('text')
      .attr('dy', '0.31em')
      .attr('x', d => d._children ? -10 : 10)
      .attr('text-anchor', d => d._children ? 'end' : 'start')
      .attr('font-size', d => d.depth === 0 ? 5 : 10 / (d.depth * 1.3))
      .text(d => {
        console.log(d)
        return d.data.title
      })
      .clone(true).lower()
      .attr('stroke-linejoin', 'round')
      .attr('stroke-width', 3)
      .attr('stroke', 'white')

    // Transition nodes to their new position.
    const nodeUpdate = node.merge(nodeEnter).transition(transition)
      .attr('transform', d => `translate(${d.y},${d.x})`)
      .attr('fill-opacity', 1)
      .attr('stroke-opacity', 1)

    // Transition exiting nodes to the parent's new position.
    const nodeExit = node.exit().transition(transition).remove()
      .attr('transform', d => `translate(${source.y},${source.x})`)
      .attr('fill-opacity', 0)
      .attr('stroke-opacity', 0)

    // Update the links…
    const link = gNode.selectAll('path')
      .data(links, d => d.target.id)

    // Enter any new links at the parent's previous position.
    const linkEnter = link.enter().append('path')
      .attr('d', d => {
        const o = {
          x: source.x0,
          y: source.y0
        }
        return diagonal({
          source: o,
          target: o
        })
      })

    // Transition links to their new position.
    link.merge(linkEnter).transition(transition)
      .attr('d', diagonal)

    // Transition exiting nodes to the parent's new position.
    link.exit().transition(transition).remove()
      .attr('d', d => {
        const o = {
          x: source.x,
          y: source.y
        }
        return diagonal({
          source: o,
          target: o
        })
      })

    // Stash the old positions for transition.
    root.eachBefore(d => {
      d.x0 = d.x
      d.y0 = d.y
    })
  }
  update(root)
}

编辑

在插入和更新时使用 getBBox,我不确定如何处理这个问题。

  const svg = d3.select('svg')
    // .attr('viewbox', [-margin.left, -margin.top, width, dx])
    .style('user-select', 'none')


  const gNode = svg.append('g')
    .attr('cursor', 'pointer')
    .attr('pointer-events', ' all')
/*     .attr('width', width)
    .attr('height', height) */

  const tree = d3.tree().nodeSize([dx, dy])

  const { x, y, w, h } = gNode.getBBox()
  svg.attr('viewbox', [x, y, width, height])

标签: javascriptd3.jssvgtreecollapse

解决方案


推荐阅读