block by timelyportfolio 414195af84d7f28b6502384c329a83a0

R htmlwidgets look at UA Calvin Ridley

Full Screen

assembled with blockbuilder.org

Kept this basic and non-custom. Use R to scrape stats from rolltide.com roster and then visualize with sunburstR and a prototype of a R-ified e2d3 dot-bar chart.

code

library(xml2)
library(rvest)
library(dplyr)

url <- "http://www.rolltide.com/services/responsive-roster-bio.ashx?type=stats&rp_id=3153&path=football&year=2016&player_id=0"
ridley <- read_html(url)

# 
games <- ridley %>%
  html_node('table') %>%
  html_nodes('tbody tr th') %>%
  html_text()

run_yards <- ridley %>%
  html_node('table') %>%
  html_nodes('td:nth-child(4)') %>%
  html_text() %>%
  purrr::map_dbl(as.numeric)

pass_yards <- ridley %>%
  html_node('table') %>%
  html_nodes('td:nth-child(8)') %>%
  html_text() %>%
  purrr::map_dbl(as.numeric)

ridley_df <- data.frame(
  game = games,
  run = run_yards[-length(run_yards)],
  pass = pass_yards[-length(pass_yards)],
  stringsAsFactors = FALSE
) %>%
  tidyr::gather(type, value, -game)

(
  sb <- ridley_df %>%
    mutate(path = paste(type, game, sep="-")) %>%
    select(path,value) %>%
    sunburstR::sunburst(percent=TRUE, count=TRUE)
)

# now build the e2d3 dot-bar chart
#  see https://github.com/timelyportfolio/e2d3R/blob/master/prototype.R
e2d3 <- htmlDependency(
  name = "e2d3",
  version = "0.6.4",
  src = c(href = "https://cdn.rawgit.com/timelyportfolio/e2d3/master/dist/lib"),
  script = "e2d3.js"
)

# make a function for now as convenience
#   to allow R data.frame in proper format
#   but eventually rewrite e2-dot-bar with arguments
#   to allow other column names for the hierarchy
e2d3_dot_builder <- function(data = NULL) {
  browsable(
    attachDependencies(
      tagList(
        tags$div(
          id = "chart"
        ),
        tags$script(HTML(
          sprintf(
            "
            var root = document.getElementById('chart');
            var data = '%s';
            %s
            var dim = { width: 600, height: 400 };
            var margin = { top: 30, bottom: 50, left: 50, right: 20 };
            var inputHeight = 20;
            var numberFormat = d3.format('.0f');
            dim.graphWidth = dim.width - margin.left - margin.right;
            dim.graphHeight = dim.height - margin.top - margin.bottom;
            require(['e2d3model'],function(model){
            var rows = d3.csv.parseRows(data);
            update(new model.ChartDataTable(rows));
            })
            ",
            paste0(
              capture.output(write.csv(data, row.names=FALSE)),
              collapse="\\n"
            ),
            paste0(
              readLines("C:\\\\Users\\KENT\\Dropbox\\development\\r\\e2d3-contrib\\dot-bar-chart\\main.js"),
              collapse="\n"
            )
          )
        ))
    ),
    list(e2d3)
  )
  )
}

e2db <- ridley_df %>%
  tidyr::spread(type, value) %>%
  mutate(Year = game) %>%
  select(Year,everything()) %>%
  select(-game) %>%
  e2d3_dot_builder()


browsable(
  tagList(
    tags$h1(
      "University of Alabama | ",
      tags$a(
        href="http://www.rolltide.com/roster.aspx?rp_id=556",
        "Calvin Ridley"
      )
    ),
    tags$h3("Sunburst from sunburstR"),
    sb,
    tags$h3("e2d3 Dot Bar Chart"),
    e2db
  )
)

index.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="https://cdn.rawgit.com/ramnathv/htmlwidgets/master/inst/www/htmlwidgets.js"></script>
<link href="https://cdn.rawgit.com/timelyportfolio/sunburstR/master/inst/htmlwidgets/lib/sequences/sequences.css" rel="stylesheet" />
<script src="https://cdn.rawgit.com/timelyportfolio/sunburstR/master/inst/htmlwidgets/sunburst.js"></script>
<script src="https://cdn.rawgit.com/timelyportfolio/e2d3/master/dist/lib/e2d3.js"></script>

</head>
<body style="background-color:white;">
<h1>
  University of Alabama | 
  <a href="//www.rolltide.com/roster.aspx?rp_id=556">Calvin Ridley</a>
</h1>
<h3>Sunburst from sunburstR</h3>
<div class="sunburst html-widget" id="htmlwidget-b6537684f1f34fc64b4f" style="width:960px;height:500px; position:relative;">
  <div>
    <div class="sunburst-main">
      <div class="sunburst-sequence"></div>
      <div class="sunburst-chart">
        <div class="sunburst-explanation" style="visibility:hidden;"></div>
      </div>
    </div>
    <div class="sunburst-sidebar">
      <input type="checkbox" class="sunburst-togglelegend">Legend</input>
      <div class="sunburst-legend" style="visibility:hidden;"></div>
    </div>
  </div>
</div>
<script type="application/json" data-for="htmlwidget-b6537684f1f34fc64b4f">{"x":{"csvdata":{"path":["run-USC","run-WKU","run-Ole Miss","run-Kent","run-Kentucky","run-Arkansas","run-Tennessee","run-Texas A&M","pass-USC","pass-WKU","pass-Ole Miss","pass-Kent","pass-Kentucky","pass-Arkansas","pass-Tennessee","pass-Texas A&M"],"value":[0,6,2,7,6,0,0,0,9,129,81,5,174,14,65,27]},"jsondata":null,"options":{"legendOrder":null,"colors":null,"percent":true,"count":true,"explanation":null,"breadcrumb":[],"legend":[],"sortFunction":null}},"evals":[],"jsHooks":[]}</script>
<h3>e2d3 Dot Bar Chart</h3>
<div id="chart"></div>
<script>
            var root = document.getElementById('chart');
            var data = '"Year","pass","run"\n"Arkansas",14,0\n"Kent",5,7\n"Kentucky",174,6\n"Ole Miss",81,2\n"Tennessee",65,0\n"Texas A&M",27,0\n"USC",9,0\n"WKU",129,6';
            //# require=d3

var dim = { width: root.clientWidth, height: root.clientHeight };
var margin = { top: 30, bottom: 50, left: 50, right: 20 };
var inputHeight = 20;
var numberFormat = d3.format('.0f');
dim.graphWidth = dim.width - margin.left - margin.right;
dim.graphHeight = dim.height - margin.top - margin.bottom;

var prev, next, trans;

d3.select('body').on('keydown', function () {
    if (d3.event.which === 39) {
        next();
    }
    if (d3.event.which === 37) {
        prev();
    }
});

function update(data) {
    d3.select(root).selectAll('*').remove();

    var svg = d3.select(root).append('svg')
      .attr({ width: dim.width, height: dim.height })
      .style({ padding: 0 });

    var axisLayer = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')').attr("id","g-axis-layer");
    var graphLayer = svg.append('g').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')').attr("id", "g-graph-layer");
    var inputLayer = svg.append('g').attr('transform', 'translate(0,' + (dim.height - inputHeight) + ')').attr("id", "g-input-layer");

    var xScale = d3.scale.ordinal().rangeBands([0, dim.graphWidth], 0.05);
    var xLocalScale = d3.scale.ordinal();
    var yScale = d3.scale.ordinal().rangePoints([dim.graphHeight, 0]);
    var colorScale = d3.scale.category10();
    var inputScale = d3.scale.ordinal().rangeBands([0, dim.width - margin.right]);

    var xAxis = d3.svg.axis().orient('bottom').scale(xScale);
    var yAxis = d3.svg.axis().orient('left').scale(yScale);

    var xAxisObj = axisLayer.append('g')
      .attr('transform', 'translate(' + 0 + ',' + dim.graphHeight + ')')
      .attr('class', 'axis')
      .call(xAxis);
    var yAxisObj = axisLayer.append('g')
      .attr('transform', 'translate(' + 0 + ',' + 0 + ')')
      .attr('class', 'axis')
      .call(yAxis);

    axisLayer.selectAll('.axis text').style('font', '14px "Lucida Grande", Helvetica, Arial, sans-serif');
    axisLayer.selectAll('.axis path.domain').style({ fill: 'none', stroke: '#000000', 'shape-rendering': 'crispEdges' });
    axisLayer.selectAll('.axis line').style({ fill: 'none', stroke: '#000000', 'shape-rendering': 'crispEdges' });

    var time = 0;
    var radius = 3;
    var mar = 0.6;
    var barWidth = 16;

    var auto = true;

    var duration = 2000;
    var delayMax = 1000;

    prev = function () {
        trans(time - 1);
    }

    next = function () {
        trans(time + 1);
    }

    var json = data.toMap({typed: true});

    var displaydata = [];
    var labels = json.keys;
    var parties = json.header;

    var partDict = {};
    parties.forEach(function (d, i) {
        partDict[d] = i;
    });
    var sums = {};
    var data = {};

    labels.forEach(function (label) {
        var r = [];
        parties.forEach(function (party) {
          r.push(+json[label][party]);
        });
        data[label] = r;
        sums[label] = d3.sum(data[label]);
    });

    var max = d3.max(labels.map(function (d) { return d3.max(data[d]); }));

    var denominator = Math.ceil(max / 1000);
    if (denominator != 1)
    {
      var legend = axisLayer.append('g')
        .attr('class', 'legend')
        .attr('transform', 'translate(-20,-10)');
      legend.append('circle')
        .attr({r: radius, fill: '#888', stroke: 'none'});
      legend.append('text')
        .attr({x: 5, y: 4, 'font-size': 12, 'font-family': '"Lucida Grande", Helvetica, Arial, sans-serif'})
        .text('=' + denominator);

      labels.forEach(function (label) {
          var r = [];
          parties.forEach(function (party) {
            r.push(Math.ceil(+json[label][party]/denominator));
          });
          data[label] = r;
          sums[label] = d3.sum(data[label]);
      });

      max = d3.max(labels.map(function (d) { return d3.max(data[d]); }));
    }
    else
    {
      axisLayer.select('g.legend').remove();
    }

    var nrow = Math.ceil(dim.graphHeight / (2 * (radius + mar)));
    barWidth = Math.ceil(max / nrow);
    yScale.domain(d3.range(nrow));
    yAxis.tickValues(d3.range(nrow).filter(function (d) { return d % 10 === 0; }));
    yAxis.tickFormat(function (d) { return (d * barWidth)*denominator; });
    xScale.domain(parties.map(function (d, i) { return i; }));
    xAxis.tickFormat(function (d) { return parties[d]; });
    xAxisObj.call(xAxis);
    yAxisObj.call(yAxis);
    xLocalScale.rangeBands([0, xScale.rangeBand()]).domain(d3.range(barWidth));
    colorScale.domain(d3.range(parties.length));

    inputScale.domain(labels);
    var currentButton = inputLayer.append('rect')
      .attr('class', 'cursor')
      .attr({ x: 0, y: 0, height: inputHeight, width: inputScale.rangeBand() })
      .style('stroke', '#FFF')
      .style('stroke-width', 2)
      .style('fill', '#000');
    var buttons = inputLayer.selectAll('.button').data(labels).enter().append('g').attr('class', 'button')
      .attr('transform', function (d) { return 'translate(' + inputScale(d) + ',' + 0 + ')'; })
      .on('click', function () {
          var s = d3.select(this);
          trans(labels.indexOf(s.datum()));
      });
    buttons.append('rect')
      .attr({ x: 0, y: 0, height: inputHeight, width: inputScale.rangeBand() })
      .style('stroke', '#FFF')
      .style('stroke-width', 2)
      .style('fill', 'rgba(0,0,0,0.1)');
    buttons.append('text')
      .text(function (d) { return d; })
      .attr('x', function (d) { return inputScale.rangeBand() / 2; })
      .attr('y', 16)
      .style('fill', function (d, i) { return (i === 0) ? '#FFF' : '#000'; })
      .style('text-anchor', 'middle')
      .style('font', (inputHeight - 4) + 'px "Lucida Grande", Helvetica, Arial, sans-serif');

    var summax = d3.max(labels.map(function (d) { return sums[d]; }));
    var displaydata = d3.range(summax).map(function (d) { return []; });
    var indexMargin = 0;
    parties.forEach(function (party, partyidx) {
        for (var i = 0; i < data[labels[0]][partyidx]; ++i) {
            displaydata[indexMargin + i].push({ label: partyidx, idx: i });
        }
        indexMargin += data[labels[0]][partyidx];
    });
    for (var i = indexMargin; i < summax; ++i) {
        displaydata[i].push({ label: null, idx: null });
    }

    d3.range(1, labels.length).forEach(function (idx) {
        var year = labels[idx];
        var lastyear = labels[idx - 1];
        var yearidx = idx;
        var pool = [];
        var unused = [];
        var keep = [];
        displaydata.forEach(function (d, i) {
            var copy = { label: d[yearidx - 1].label, idx: d[yearidx - 1].idx };
            d.push(copy);
            if (d[yearidx].label == null) {
                unused.push(i);
            }
            else {
                if (data[year][d[yearidx].label] <= d[yearidx].idx) {
                    pool.push(i);
                }
                else {
                    keep.push(i);
                }
            }
        });

        d3.shuffle(pool);
        if (sums[year] - sums[lastyear] > 0) {
            pool = pool.concat(unused.splice(0, sums[year] - sums[lastyear]));
            d3.shuffle(pool);
        }
        else {
            pool.splice(sums[year] - keep.length).forEach(function (d) {
                displaydata[d][yearidx] = { label: null, idx: null };
            });
            pool = pool.splice(0, sums[year] - keep.length);
        }
        var poolmargin = 0;

        parties.forEach(function (party) {
            if (data[year][partDict[party]] - data[lastyear][partDict[party]] > 0) {
                for (var i = 0; i < (data[year][partDict[party]] - data[lastyear][partDict[party]]) ; ++i) {
                    if (pool[poolmargin + i]) displaydata[pool[poolmargin + i]][yearidx] = { label: partDict[party], idx: i + data[lastyear][partDict[party]] };

                };
                poolmargin += data[year][partDict[party]] - data[lastyear][partDict[party]];
            }
        });

    });
    var votes = graphLayer.selectAll('.vote').data(displaydata).enter().append('circle')
      .attr('class', 'vote')
      .attr('r', radius)
      .attr('cx', function (d) { return ((d[time].label != null) ? (xScale(d[time].label) + xLocalScale(d[time].idx % barWidth) + radius + mar) : (dim.graphWidth / 2)); })
      .attr('cy', function (d) { return ((d[time].label != null) ? (yScale(Math.floor((d[time].idx + 0.1) / barWidth)) - radius - mar) : 0); })
      .style('opacity', function (d) { return (d[time].label != null) ? 0.8 : 0.0; })
      .style('fill', function (d) { return colorScale(d[time].label); });

    trans = function (to) {
        if (to === time || to < 0 || to >= labels.length) {
            return;
        }
        var current = time;
        time = to;
        yearTarget = labels[time];
        var votes = graphLayer.selectAll('.vote')
          .filter(function (d) { return d[current].label != d[time].label || d[current].idx != d[time].idx; })
          .transition()
          .duration(duration)
          .delay(function (d) { return Math.random() * delayMax; })
          .attr('cx', function (d) { return ((d[time].label != null) ? (xScale(d[time].label) + xLocalScale(d[time].idx % barWidth) + radius + mar) : (dim.graphWidth / 2)); })
          .attr('cy', function (d) { return ((d[time].label != null) ? (yScale(Math.floor((d[time].idx + 0.1) / barWidth)) - radius - mar) : 0); })
          .style('opacity', function (d) { return (d[time].label != null) ? 0.8 : 0.0; })
          .style('fill', function (d) { return colorScale(d[time].label); });

        inputLayer.select('.cursor').transition().duration(duration / 2)
          .attr('x', function (d) { return inputScale(labels[time]); });
        inputLayer.selectAll('.button text').transition().duration(duration / 2)
          .style('fill', function (d, i) { return (i === time) ? '#FFF' : '#000'; })
    }
};
            var dim = { width: 600, height: 400 };
            var margin = { top: 30, bottom: 50, left: 50, right: 20 };
            var inputHeight = 20;
            var numberFormat = d3.format('.0f');
            dim.graphWidth = dim.width - margin.left - margin.right;
            dim.graphHeight = dim.height - margin.top - margin.bottom;
            require(['e2d3model'],function(model){
            var rows = d3.csv.parseRows(data);
            update(new model.ChartDataTable(rows));
            })
            </script>
</body>
</html>