All examples By author By category About

timelyportfolio

partsankey on rpart

This is the derivative version of the partition sankey, but instead of collapsing leaves, this partsankey visualizes rpart or recursive partitioning. See post.

Replicate in R

library(htmltools)
library(rpart)
library(d3r)
library(vcdExtra)  # for the Titanic dataset used in first post

data("Titanicp", package="vcdExtra")

fit <- rpart(
  survived ~ pclass + sex + age,
  data = Titanicp
)

fit_json <- d3_party(fit)

scr <- tags$script(HTML(
sprintf(
"
  var titanic = %s

  var hier = d3.hierarchy(titanic)
  var width = 800;
  var height = 600;
  var nodeWidth = 10;
  var color = d3.scaleOrdinal(d3.schemeCategory10);
  // rearrange so that survived yes is green
  //  and no is red
  color.range(
    [color.range()[2]]
      .concat([color.range()[3]])
      .concat(color.range().splice(0,2))
      .concat(color.range().splice(4,10))
  )
  color.domain(['2', '1'])

  
  var svg = d3.select('#vis').append('svg')
    .style('width',width + 20 + 20)
    .style('height',height + 20 + 20)
    .append('g')
    .attr('transform','translate(20,20)');

  // n will represent sum at each split
  //  so don't use hier.sum
  //  instead add value attribute
  hier.each(function(d) {d.value = d.data.wt})

  // caution will mutate the object
  layout(hier, nodeWidth, 0.75)

  // note, this will not follow traditional d3 call form
  //  if we were to make this official change this to svg.call
  var nodes = drawNodes(hier, svg)

  var links = drawLinks(nodes, svg)

  // add mouseover that highlights with higher opacity
  //  this will highlight all children with same leaf name
  svg.selectAll('.link').on('mouseover', function(d) {
    d3.select(this).style('opacity', 0.8)
    if(d.target.linkPaths) {
      d.target.descendants().forEach(function(dd) {
        if(dd.linkPaths){
          dd.linkPaths.forEach(function(lp) {
            var data = d3.select(lp).datum();
            if(data.leaf == d.leaf) {
              d3.select(lp).style('opacity', 0.8)
            }
          })
        }
      })
    }
  })

  svg.selectAll('.link').on('mouseout', function(d) {
    svg.selectAll('.link').style('opacity', 0.5)
  })

  function layout(hier, nodeWidth, nodeHeightRatio) {
    nodeWidth = nodeWidth ? nodeWidth : 10;
    nodeHeightRatio = nodeHeightRatio ? nodeHeightRatio : 0.66;
    var nodeHeight = height * nodeHeightRatio;
    
    // run treemap slice to get heights
    //  note, this will overwrite the x0,x1,y0,y1
    d3.treemap()
      .size([nodeWidth, nodeHeight])
      .tile(d3.treemapSlice)(hier);

    // record treemap heights and widths
    //  since partition will overwrite
    hier.each(function(d) {
      d.h = d.y1 - d.y0;
      d.w = d.x1 - d.x0;
    })

    // now run partition with no size
    //  so will be in range [0,1]
    d3.partition()(hier);
  }

  function drawNodes(hier, svg) {
    var nodes = svg.selectAll('g.node')
      .data(hier.descendants());
    
    nodes = nodes.merge(
      nodes.enter().append('g')
        .attr('class','node')
    );
    
    nodes.attr('transform', function(d) {
      return 'translate(' + d.y0 * width  + ',' + d.x0 * height + ')'
    });
    
    nodes.append('rect')
      .classed('rect-part', true);
    
    nodes.append('text')
      //.style('text-anchor','end')
      .attr('dy','0.5em')
      .text(function(d) { return d.data.description});

    nodes.selectAll('rect.rect-part')
      .style('fill', 'gray')//function(d) {return color(d.data.name)})
      .style('stroke', 'white')
      //.transition()
      //.duration(duration)
      //.delay(delay)
      .attr('y', function(d) {
        return ((d.x1-d.x0)*height-d.h)/2;
      })
      .attr('width', function(d) { return nodeWidth; })
      .attr('height', function(d) {
        return d.h;
      });
      
  nodes.selectAll('text')
    .attr('y', function(d) {
      if(d.height > 0) {
      return ((d.x1-d.x0)*height-d.h)/2;
    }
      return (d.x1-d.x0)*height/2;
    })
    .attr('x', function(d) {
      if(d.height > 0) {
        return nodeWidth / 2
      }
      return nodeWidth
    })
    .attr('dy', function(d) {
      if(d.height > 0) {
        return '-0.15em'
      }
      return '0.25em'
    })
    .style('text-anchor', function(d) {
      if(d.height > 0) {
        return 'middle'
      }
      return 'start'
    })

  
      
    return nodes;    
  }

  function drawLinks(nodes, svg) {
    function stackSource(x) {
      var xobj = {}
      var sum = x.data.wt
      x.children.forEach(function(d) {
        // sum by response by using leaf data
        var leafdat = d.leaves().map(function(lf) {
          return lf.data.size.size[0]
        })
        var sizes = d3.zip(
          d3.merge(leafdat.map(function(d){return d.response})),
          d3.merge(leafdat.map(function(d){return d.freq}))
        )
        var sumsResponse = d3.nest()
          .key(function(d){return d[0]})
          .rollup(function(d){
            return d3.sum(d.map(function(dd){return dd[1]}))
          })
          .entries(sizes)
        sumsResponse.forEach(function(dd) {
          xobj[d.data.id + '~' + dd.key] = dd.value/sum;
        })
      })

      return d3.stack().keys(Object.keys(xobj))([xobj]);
    }

    function stackTarget(x) {
      // sum by response by using leaf data
      var leafdat = x.leaves().map(function(d) {
        return d.data.size.size[0]
      })
      var sizes = d3.zip(
        d3.merge(leafdat.map(function(d){return d.response})),
        d3.merge(leafdat.map(function(d){return d.freq}))
      )
      var sumsResponse = d3.nest()
        .key(function(d){return d[0]})
        .rollup(function(d){
          return d3.sum(d.map(function(dd){return dd[1]}))
        })
        .entries(sizes)
      var xobj = {};
      var sum = d3.sum(sumsResponse.map(function(d){
        return d.value
      }))
      sumsResponse.forEach(function(d) {
        xobj[d.key] = d.value/sum;
      })
      return d3.stack().keys(Object.keys(xobj))([xobj]);
    }

  
    nodes.each(function(node) {
      if(node.height == 0) {return}

      // empty array to store links on each node
      node.linkPaths = node.linkPaths ? node.linkPaths : [];
  
      function customLine(pts) {
        var ld = d3.linkHorizontal()
          .source(function(d){return d[0]})
          .target(function(d){return d[1]})
          .x(function(d){return d[0]})
          .y(function(d){return d[1]});
        return [
          ld([pts[0],pts[1]]),
          pts[1] + ',' + pts[2],
          ld([pts[2],pts[3]]).slice(1)
        ].join('L');
      }

      var st1 = stackSource(node);

      st1.forEach(function(d) {
        var childname = d.key.split('~')[0]
        var leafname = d.key.split('~')[1]

        var child = node.children.filter(function(ch) {
          return ch.data.id == childname
        })[0]

        var st = stackTarget(child).filter(function(d) {
          return d.key === leafname
        })[0];

        var link = svg.append('path')
          .classed('link', true)
          .style('opacity', 0.000001)

        link.datum({
          source: node,
          target: child,
          leaf: leafname
        })

        link.attr('d',customLine([
          [node.y0 * width + nodeWidth, ((node.x0 * height) + ((node.x1-node.x0)*height-node.h)/2 ) + d[0][0]*node.h],
          [child.y0 * width, ((child.x0 * height) + ((child.x1-child.x0)*height-child.h)/2 ) + st[0][0]*child.h],
          [child.y0 * width, ((child.x0 * height) + ((child.x1-child.x0)*height-child.h)/2 ) + st[0][1]*child.h],
          [node.y0 * width + nodeWidth, ((node.x0 * height) + ((node.x1-node.x0)*height-node.h)/2 ) + d[0][1]*node.h],
        ]));
        
        link
          .style('stroke', 'white')
          .style('fill', color(leafname));
        
        link
          .transition()
          //.duration(500)
          //.delay(duration+delay)
          .style('opacity', 0.5);
        
        node.linkPaths = node.linkPaths.concat(link.node());
      })
    })
  }
"
  ,
  fit_json
)
))

browsable(
  tagList(
    d3r::d3_dep_v4(offline=FALSE),
    tags$div(id = "vis"),
    scr
  )
)

forked from timelyportfolio's block: partsankey static

forked from timelyportfolio's block: partsankey interactive

forked from timelyportfolio's block: partsankey interactive angled