Skip to content

Instantly share code, notes, and snippets.

@timelyportfolio
Last active October 26, 2016 14:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timelyportfolio/414195af84d7f28b6502384c329a83a0 to your computer and use it in GitHub Desktop.
Save timelyportfolio/414195af84d7f28b6502384c329a83a0 to your computer and use it in GitHub Desktop.
R htmlwidgets look at UA Calvin Ridley
license: mit
scrolling: yes
height: 400

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
  )
)
<!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="http://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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment