chartexample.com
  • About
  • Blog
  • Charts

Tree chart D3 example

  • Home
  • Charts
  • Tree
  • Tree chart D3 example
function makeTree() {
  //data set
  const data = {
    name: 'root',
    children: [
      {name: 'child 1'},
      {
        name: 'child 2',
        children: [
          {name: 'child 4'},
          {name: 'child 5'}
        ]
      },
      {
        name: 'child 3',
        children: [
          {name: 'child 6'}
        ]
      }
    ]
  }

  const strokeColor = '#d0d4fc'
  const fillColor = '#285674'
  const selectionColor = '#4b59d2'

  // selecting root element for plot
  const svg = d3.select('#chart')
    // setting cursor style
    .style('cursor', 'pointer')

  // getting root element width
  const width = parseInt(svg.style('width')) 
  // getting root element height
  const height = parseInt(svg.style('height')) 

  // tree node height
  const nodeHeight = 50
  // tree node size
  const nodeWidth = 120
  // length of space between nodes
  const nodeSeparation = 30

  // creating zoom behaviour
  const zoom = d3.zoom()
      // setting extent for zooming
      .scaleExtent([1, 2])
      // setting callback event (both scrolling and zooming)
      .on('zoom', onzoom)

  // creating tree hierarchy from the data
  const nodes = d3.hierarchy(data, d => d.children)
  // creating generator for link paths
  // (x and y coords should be swapped for horizontal rendering)
  const lnkMkr = d3.linkHorizontal().x( d => d.y ).y( d => d.x )

  // creating of common component for all plot elements
  const g = svg.append('g')

  svg
    // setting initial transform for plot
    .call(
      // calling zoom.transform manually
      zoom.transform,
      // passing initial transform params by calling translate func
      d3.zoomIdentity.translate(width / 4, height / 2)
    )
    // applying zooming behaviour to plot
    .call(zoom)

  // creating defs element for plot
  // which contain base element 
  // for tree nodes
  svg.append('defs')
    // creating base tree node element
    .append('rect')
    // setting id for base tree node element
    .attr('id', 'node')
    // setting width for nodes
    .attr('width', nodeWidth)
    // setting height for nodes
    .attr('height', nodeHeight)
    // setting border radius
    .attr('rx', '20')
    .attr('ry', '20')
    // setting stroke color
    .style('stroke', strokeColor)
    // setting stroke width
    .style('stroke-width', 2)


  // applying tree layout to hierarchy
  d3.tree()
    // setting nodes size
    .nodeSize([
      nodeHeight + nodeSeparation,
      nodeWidth + nodeSeparation
    ])(nodes)

  // applying links to the plot
  // creating empty selection of path elements
  const links = g.selectAll('path')
    // applying links data from hierarchy
    .data(nodes.links())
    // getting all new data connections
    .enter()
    // appending real path elements 
    .append('path')
    // setting path d param by using links generator
    .attr('d', d => lnkMkr(d))
    // setting stroke color
    .style('stroke', '#a9a9b3' )
    // setting fill property 
    // (none value because it should be just line)
    .style('fill', 'none')

  // applying nodes elements
  // creating empty selection
  g.selectAll('g')
    // applying nodes data from hierarchy
    // .descendants returns entire hierarchy
    .data( nodes.descendants() )
    // getting all new data connections
    .enter()
    // calling node element component function
    .call(renderItem)

  // component function for tree nodes
  function renderItem(selection) {
    // creating wrapper for node elements
    const g = selection.append('g')
      // setting text positioning style
      .style('text-anchor', 'middle')
      // setting mouseenter callback
      .on('mouseenter', function(e, d) {
        // getting path from root node to current node
        // current node is which has mouseover
        const path = nodes.path(d)

        // changing color for links (path elements)
        // which connects nodes in path
        links
          // setting stroke style
          .style(
            'stroke',
            // checking that both linked nodes are in selected path
            d => path.includes(d.target) && path.includes(d.source) 
              // setting specific color
              ? selectionColor : strokeColor
          )

        // changing fill color for nodes which in selected path
        // selecting all rect in node elements in plot
        // use elements should be selected because all nodes reusing rect
        // element from defs
        g.selectAll('use')
          // applying fill style for every node element
          .style(
            'fill',
            // checking is element included in selected path
            // and selecting specific color
            d => path.includes(d) ? selectionColor : fillColor
          )
          
      })
      // setting mouseleave event
      // to reset all styles for links and nodes in plot
      .on('mouseleave', function() {
        g.selectAll('use')
          .style('fill', fillColor)
        
        links.style('stroke', strokeColor)
      })

    // appending node element by using 
    // base rect element from defs
    // appending use element
    g.append('use')
      // setting href param on base element to reuse
      .attr('href', '#node')
      // setting x coord for rect element
      // with some correction for better positioning
      .attr('x', d => d.y - 60 )
      // setting y coord for rect element with some correction
      .attr('y', d => d.x - 25 )
      // setting rect background color
      .style('fill', fillColor)
      
    // appending text element
    g.append('text')
      // coords are swapped for horizontal tree positioning
      // setting x coord
      .attr('x', d => d.y)
      // setting y coord
      .attr('y', d => d.x + 5)
      // setting text
      .text(d => d.data.name)
      .attr('fill', 'currentColor')
  }
  // zoom event callback
  function onzoom({ transform }) {
    // setting new transform coords for items container
    // because this is an element which should be
    // changed on zoom or scrolling
    g.attr('transform', transform)
  }
}

Alex Chirkin
Powered by Hugo