block by dhoboy 4ef99e7eb2147c95ab211da564e75b6a

Pitcher Explorer

Full Screen

Baseball Pitcher Explorer (open in new tab to see full block)

Brush along one or more dimensions to see a table of Pitchers that match your critera. Output table code from this syntagmatic block.

I scraped the data from Retrosheet with Python’s BeautifulSoup. Scraping repo here.

Retrosheet data use statement: ‘The information used here was obtained free of charge from and is copyrighted by Retrosheet. Interested parties may contact Retrosheet at “www.retrosheet.org".'

index.html

<!doctype html>
<meta charset="utf-8">
<title>Pitchers</title>
<style>
  body {
    font-family: sans-serif;
  }
  #main {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    justify-content: flex-start;
    margin: 20px 30px 20px 10px;
  }
  .title {
    margin-left: 9px;
    font-size: 14px;
  }
  .bar, .selection {
    fill: steelblue;
  }
  .axis path,
  .axis line {
    fill: none;
    stroke: #666666;
    shape-rendering: crispEdges;
  }
  .axis text {
    fill: #666666;
    font-size: 14px;
  }
  #output {
    margin: 0 20px 10px 10px;
  }
  pre {
    width: 100%;
    height: 300px;
    margin: 6px 12px;
    tab-size: 30;
    font-size: 10px;
    overflow: auto;
  }
</style>
<body>
<div id="main"></div>
<div id="output"></div>
<script src="https://d3js.org/d3.v4.js"></script>
<script>

var margin = {
  top: 20,
  right: 20,
  bottom: 20,
  left: 10
};

var height = 100 - margin.top - margin.bottom;
var width = 200 - margin.left - margin.right;

d3.csv("//dhoboy.github.io/baseball/pitchers.csv", function(data) {
  var filteredData = data;
  var filters = {};

  var columnKey = {
    'G': 'Games Pitched',
    'GS': 'Games Started',
    'CG': 'Complete Games',
    'SHO': 'Shutouts',
    'GF': 'Relief Games Finished',
    'SV': 'Saves',
    'IP': 'Innings Pitched',
    'H': 'Hits Allowed',
    'BFP': 'Batters Faced Pitcher',
    'HR': 'Home Runs Allowed',
    'R': 'Runs Allowed',
    'ER': 'Earned Runs Allowed',
    'BB': 'Bases On Balls',
    'IB': 'Intentional Bases On Balls',
    'SO': 'Strikeouts',
    'SH': 'Sacrifice Hits Allowed',
    'SF': 'Sacrifice Flies Allowed',
    'WP': 'Wild Pitches',
    'HBP': 'Hit By Pitch',
    'BK': 'Balks',
    'GDP': 'Grounded in Double Plays',
    'W': 'Wins',
    'L': 'Losses',
    'ERA': 'Earned Run Average',
    'RS': 'Run Support',
    'PW': 'Pitcher Wins'
  };

  var output = d3.select("#output").append("pre")
    .text("Brush along at least one dimension above to see a table of results");

  var scales = d3.keys(columnKey).reduce(function(prev, next) {
    prev[next] = d3.scaleLinear()
                  .domain(d3.extent(data, function(d) {
                    return +d[next];
                  }))
                  .range([0, width]);
    return prev;
  }, {});

  var reverseScales = d3.keys(columnKey).reduce(function(prev, next) {
    prev[next] = d3.scaleLinear()
                  .domain([margin.left, width + margin.left, height])
                  .range(d3.extent(data, function(d) {
                    return +d[next];
                  }));
    return prev;
  }, {});

  var axes = d3.keys(columnKey).reduce(function(prev, next) {
    prev[next] = d3.axisBottom(scales[next]).ticks(3)
    return prev;
  }, {});

  var brushes = d3.keys(columnKey).reduce(function(prev, next) {
    prev[next] = d3.brushX().extent([[margin.left, -15], [width + margin.left, height]]);
    return prev;
  }, {});

  var graphs = d3.select("#main").selectAll(".graph")
    .data(d3.keys(columnKey))
    .enter()
    .append("div")
    .attr("class", "graph");

  graphs.append("div")
    .attr("class", "title")
    .text(function(d) { return columnKey[d] + ": " + d; });

  var svgs = graphs.append("svg")
    .attr("id", function(d) { return d; })
    .attr("height", height + margin.top + margin.bottom)
    .attr("width", width + margin.right + margin.left)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  svgs.append("rect")
    .attr("class", "bar")
    .attr("height", 15)
    .attr("width", width)
    .attr("rx", 5)
    .attr("ry", 5);

  d3.keys(columnKey).forEach(function(key) {
    var s = d3.select("svg#" + key);

    s.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(" + margin.left + "," + height + ")")
      .call(axes[key]);

    s.append("g")
      .attr("class", "brush")
      .call(brushes[key]);

    brushes[key].on("brush end", function() {
      var brushSection = d3.brushSelection(this);

      if (brushSection === null) {
        removeFilter(key);
      } else {
        var filterInput = [
          reverseScales[key](brushSection[0]),
          reverseScales[key](brushSection[1])
        ];
        addFilter(key, filterInput);
      }
    });
  });

  function addFilter(key, filterInput) {
    filters[key] = filterInput;
    filterData();
  }

  function removeFilter(key) {
    delete filters[key];
    filterData();
  }

  function filterData() {
    // reset filteredData
    filteredData = data;

    // apply each filter
    d3.keys(filters).forEach(function(filterKey) {
      filteredData = filteredData.filter(function(d) {
        return +d[filterKey] >= filters[filterKey][0] &&
          +d[filterKey] <= filters[filterKey][1];
      });
    });

    // only draw result table if you've filtered the dataset somewhat
    if (d3.keys(filters).length > 0) {
      output.text(d3.tsvFormat(filteredData.slice(0)));
    } else {
      output.text("Brush along at least one dimension above to see a table of results");
    }
  }
});
</script>