block by timelyportfolio 9a7aa89d3c18f5a436ae50040191d882

parttree (curved) on Titanic

Full Screen

This example courtesy of Displayr who have generously offered to sponsor a series of independently authored posts about interactive visualization with R and JavaScript. Thank you so much Displayr for this opportunity.


Code in R

Below is the R source code for creating this visualization. You might notice that much of this is actually JavaScript. To see the JavaScript, please inspect index.html.

(function() {
library(htmltools)
library(d3r)
library(dplyr)

  titan_nest <<- as.data.frame(Titanic) %>%
  select(-Age) %>%
  group_by(Class, Sex, Survived) %>%
  summarise(Freq = sum(Freq)) %>%
  d3_nest(value_cols="Freq", root="Titanic")
})()


scr_part <- function() {
  tags$script(HTML(
sprintf(
"
function initialize(hier) {
  d3.partition().size([height,width])(hier);

  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  + ',' + d.x0 + ')'
  });

  nodes.append('rect')
    .classed('rect-part', true);

  nodes.append('rect')
    .classed('rect-outline', true)
    .attr('width', function(d) { return d.y1 - d.y0; })
    .attr('height', function(d) { return d.x1 - d.x0; })
    .style('fill', 'none')
    .style('stroke', 'none');

  nodes.append('text')
    //.style('text-anchor','end')
    .attr('dy','1em')
    .text(function(d) { return d.data.name});

  return nodes;
}

function drawPart(hier, nodes, duration, delay) {
  d3.partition().size([height,width])(hier);

  svg.selectAll('.link')
    .transition()
    .duration(100)
    .style('opacity', 0.00001)
    .remove();

  nodes.selectAll('rect.rect-part')
    .transition()
    .duration(duration)
    .delay(delay)
    .attr('width', function(d) { return d.y1 - d.y0; })
    .attr('height', function(d) { return d.x1 - d.x0; })
    .attr('x', 0)
    .attr('y', 0)
    .style('fill', function(d) { return color(d.data.name) || 'gray'; })
    .style('stroke', 'white');

  nodes.selectAll('rect.rect-outline')
    .transition()
    .duration(duration)
    .delay(delay)
    .style('stroke', 'white')
    .style('fill', 'none')
    .style('stroke-dasharray', null);
}
",
titan_nest
)
))
}


scr_part_tree <- function() {
  tags$script(HTML(
sprintf(
"
function drawPartTree(hier, nodes, duration, delay) {
  var nodeHeight = height * 0.66;
  var nodeWidth = 10;

  // 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);

  nodes.selectAll('rect.rect-outline')
    .transition()
    .duration(duration)
    .delay(delay)
    .style('stroke', 'gray')
    .style('fill', 'none')
    .style('stroke-dasharray','2,2');

  nodes.selectAll('rect.rect-part')
    .style('fill', function(d) { return color(d.data.name) || 'gray'; })
    .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;
    });

  function stack(x) {
    var xobj = {};
    var sum = d3.sum(x.children, function(d) {return d.h} )
    x.children.forEach(function(d) {
      xobj[d.data.name] = d.h/sum;
    })
    return d3.stack().keys(Object.keys(xobj))([xobj]);
  }

  nodes.each(function(node) {
    if(!node.children) {return}
    var st = stack(node);
    st.forEach(function(d,i) {
      var child = node.children[i]
      var link = svg.append('path')
        .classed('link', true)
        .style('opacity', 0.000001);

      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');
      }

      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 )],
        [child.y0 * width, ((child.x0 * height) + ((child.x1-child.x0)*height-child.h)/2 ) + child.h],
        [node.y0 * width + nodeWidth, ((node.x0 * height) + ((node.x1-node.x0)*height-node.h)/2 ) + d[0][1]*node.h],
        //[node.y0 * width + nodeWidth, ((node.x0 * height) + ((node.x1-node.x0)*height-node.h)/2 )]
      ]));

      link
        .style('stroke', 'white')
        .style('fill', color(child.data.name));

      link
        .transition()
        .duration(500)
        .delay(duration+delay)
        .style('opacity', 0.7);
    })
  });
}

var tm = %s;

var tm_h = d3.hierarchy(tm)
  .sum(function(d){
  return d.Freq || 0;
});

var width = 400;
var height = 400;

var convert_btn = d3.select('body').append('div')
  .append('button')
  .text('convert')
  .attr('disabled', '')
  .on('click', function(d) {
    if(!svg.node().state) {return}
    if(svg.node().state === 'partition') {
      drawPartTree(tm_h, nodes, 2000, 0);
      svg.node().state = 'partitionTree'
    } else {
      drawPart(tm_h, nodes, 2000, 0);
      svg.node().state = 'partition'
    }
  })

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

var color = d3.scaleOrdinal(d3.schemeCategory10);

var nodes = initialize(tm_h);

drawPartTree(tm_h, nodes, 0,0);
svg.selectAll('rect.rect-outline').remove();
svg.selectAll('text').remove();

convert_btn.attr('disabled', null);
svg.node().state = 'partitiontree'
",
titan_nest
)
))
}


### create partition and partition tree ####
browsable(
  tagList(
    d3_dep_v4(offline=FALSE),
    scr_part(),
    scr_part_tree()
  )
)

forked from timelyportfolio‘s block: parttree on Titanic

index.html