IMDbTvShowStatistics

Shows season statistics for TV Shows on IMDB

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        IMDbTvShowStatistics
// @namespace   notableTieView
// @author      notableTieView
// @description Shows season statistics for TV Shows on IMDB
// @include     http://www.imdb.com/title/*
// @include     http://www.imdb.com/title/*/eprate*
// @version     1.6
// @grant       none
// @license Creative Commons Attribution-NonCommercial 3.0 http://creativecommons.org/licenses/by-nc/3.0/
//
// This script uses the following external libraries which are available under different licenses:
// jQuery (https://jquery.com/) is provided under the MIT License https://tldrlegal.com/license/mit-license
// d3 (http://d3js.org/) is provided under the BSD 3-Clause License https://github.com/mbostock/d3/blob/master/LICENSE
// Chart.js (http://www.chartjs.org/) is provided under the MIT License http://opensource.org/licenses/MIT
// Highcharts (http://shop.highsoft.com/highcharts.html) is provided by Highsoft (http://shop.highsoft.com/) for non-commercial use under the Creative Commons Attribution-NonCommercial 3.0 license: http://creativecommons.org/licenses/by-nc/3.0/
//
// @require https://code.jquery.com/jquery-2.1.4.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/Chart.js/1.0.2/Chart.min.js
// @require http://code.highcharts.com/highcharts.js
// @require http://code.highcharts.com/highcharts-more.js
// @require http://code.highcharts.com/modules/exporting.js
//
// ==/UserScript==

// compatibility
this.$ = this.jQuery = jQuery.noConflict(true);

var processedRows=0;

/*
Plot a box plot for every season (min, q1, median, q3, max)
divId - the div to add the plot to
plotData - the data
*/
function plotBoxPlot(divId, plotData, width) {
  $(divId).highcharts({
    chart: {
      type: 'boxplot',
      width: width,
      height: 200
    },
    title: {
      text: ''
    },
    legend: {
      enabled: false
    },
    xAxis: {
      categories: plotData.labels,
      title: {
        text: '' //Seasons'
      }
    },
    yAxis: {
      title: {
        text: ''
      },
      plotLines: [
        {
          value: plotData.median,
          color: 'red',
          width: 2,
          label: {
            align: 'left',
            style: {
              color: 'gray'
            }
          }
        }
      ]
    },
    series: [
      {
        name: 'Stats',
        data: plotData.quartiles,
        tooltip: {
          headerFormat: '<em>Season {point.key}</em><br/>'
        }
      }
    ]
  });
}
/*
Compute the average over an array
ar - the array (numeric)
*/

function getAverage(ar) {
  var count = 0;
  for (var i = 0, n = ar.length; i < n; i++) {
    count += ar[i];
  }
  return count / ar.length;
}
/*
Get a quartile of an array
ar - array, sorted (highest first)
q - the quartile we want: e.g. 0.25, 0.5 (median), 0.75
*/

function getQuartile(q, ar) {
    var realQ = (1 - q) * ar.length;
    var floorQ = Math.floor(realQ);
    if (realQ === floorQ) {
      if (floorQ === 0) {
        return ar[0];
      } else if (floorQ === ar.length) {
        return ar[ar.length - 1];
      } else {
        return getAverage([ar[floorQ - 1], ar[floorQ]]);
      }
    }
    return ar[floorQ];
}
/*
Transform seasonData into data structure thats easy to use for plotting
seasonData - a hash with an array of ratings for each season 
the index in the hash is the seaons, season 0 is for specials
*/

function getPlotData(seasonData) {
  seasons = Object.keys(seasonData);
  n = seasons.length;
  seasonLabels = [
  ];
  seasonAverages = [
  ];
  seasonQuartiles = [
    []
  ];
  allRatings = [
  ];
  specials = false;
  seasons.forEach(function (season) {
    index = season - 1;
    if (season == 0) {
      index = n - 1;
      seasonLabels[index] = 'S'; //specials';
      specials = true;
    } else {
      seasonLabels[index] = season.toString();
    }
    seasonAverages[index] = getAverage(seasonData[season]);
    seasonQuartiles[index] = [
      Math.min.apply(null, seasonData[season]),
      getQuartile(0.25, seasonData[season]),
      getQuartile(0.5, seasonData[season]),
      getQuartile(0.75, seasonData[season]),
      Math.max.apply(null, seasonData[season])
    ];
    allRatings = allRatings.concat(seasonData[season]);
  });
  allRatings.sort(function (a, b) {
    return b - a
  }); //sort in reverse order
  averageData = {
    averages: seasonAverages,
    labels: seasonLabels,
    quartiles: seasonQuartiles,
    minimumFloor: Math.max(Math.floor(Math.min.apply(null, seasonAverages)) - 1, 0),
    median: getQuartile(0.5, allRatings),
    num: n,
    specials: specials
  };
  return averageData;
}
/*
Plot a simple chart with season averages as bars
divId - the id to add the plot to
plotData - the datastructure 
*/

function plotChart(divId, plotData) {
  var data = {
    labels: plotData.labels,
    datasets: [
      {
        label: 'Average Season Ratings',
        fillColor: 'rgba(19,108,178,0.5)',
        strokeColor: 'rgba(220,220,220,0.8)',
        highlightFill: 'rgba(19,108,178,0.9)',
        highlightStroke: 'rgba(220,220,220,1)',
        data: plotData.averages
      }
    ]
  };
  var options = {
    scaleOverride: true,
    scaleStepWidth: 1,
    scaleSteps: (10 - averageData.minimumFloor),
    scaleStartValue: averageData.minimumFloor,
  }
  var ctx = $(divId).get(0).getContext('2d');
  new Chart(ctx).Bar(data, options);
}

/*
Get the suffix for a title attribute 
if the season is 0, return the title for specials,
otherwise return the title for that season
*/
function getTitleSuffix(season) {
  titleSuffix=' episode of Season '.concat(season).concat('.')
  if (season==0) {
    titleSuffix=' special.'
  } 
  return titleSuffix;
}

/*
Add css classes to highlight the top three episodes of a season
row - the current row in the episodes table
season - the season of that row
seasonScores - the extracted ratings for that season so far (including the current row)
Since the episodes are ordered from best to worst, the first three encountered rows of a season are the top three episodes
*/
function colorizeTopEpisodes(row, season, seasonScores) {
  className='#000000';
  place='';
  switch(seasonScores.length) {
    case 1:
      className='seasonBest';
      place='best';
      break;
    case 2:
      className='seasonSecond';
      place='second best';
      break;
    case 3:
      className='seasonThird';
      place='third best';
      break;
    default:
      return;
  }
  $(row).children().eq(0).addClass(className);
  $(row).children().eq(0).attr('title', 'The '.concat(place).concat(getTitleSuffix(season)));
}


/*
Colorize the worst Episode of each season
* scoresBySeason - a season-to-ratings map. Needed to check that there are more than three episodes in that season
* worstEpisodesRows - a season-to-row map. The row is that of the worst episode in a season.
Colored is the worst episode in a season unless the season had three or less episodes (in that case, the worst episode is one of the top three and thus colored respectively):
*/
function colorizeWorstEpisodes(scoresBySeason, worstEpisodesRows) {
  seasons = Object.keys(scoresBySeason);
  seasons.forEach(function(season) {
    if (scoresBySeason[season].length>3) {
      row=worstEpisodesRows[season];
      $(row).children().eq(0).addClass("seasonWorst");
      $(row).children().eq(0).attr('title', 'The worst'.concat(getTitleSuffix(season)));
    }
  });
}


/*
Extract the data from one table row of the ratings table
row - the row (jquery object)
scoresBySeason - the hash of arrays to append the extracted rating to
*/

function workOnTableRow(row, scoresBySeason, worstEpisodesRows) {
  seasonEpisodeField = $(row).children().eq(0);
  if (seasonEpisodeField.is('th')) {
    $(row).children().eq(3).before('<th>User<br/>Rank</th>');
  } else {
    seasonEpisode = $.trim(seasonEpisodeField.text());
    season = 0;
    if (seasonEpisode != '-') {
      season = seasonEpisode.split('.') [0];
    }
    rating = parseFloat($(row).children().eq(2).text());
    if (season in scoresBySeason) {
      scoresBySeason[season].push(rating);
    } else {
      scoresBySeason[season] = [
        rating
      ];
    }
    colorizeTopEpisodes(row, season, scoresBySeason[season]);
    worstEpisodesRows[season] = row;
    processedRows++;
    $(row).children().eq(3).before('<td align="right">'.concat(processedRows).concat('</td>'));
    $(row).children().eq(4).attr('bgcolor', '#eeeeee');
  }
}


/*
Extract a hash with rating arrays for each season from the ratings table
*/

function collectDataPointsBySeasonAndAddRanks() {
  scoresBySeason = {
  };
  worstEpisodesRows = {};
  tabRowsVar = $('#tn15content table').eq(0).find('tr');
  tabRowsVar.each(function () {
    workOnTableRow(this, scoresBySeason, worstEpisodesRows);
  });
  colorizeWorstEpisodes(scoresBySeason, worstEpisodesRows);
  return scoresBySeason;
}
/*
Plot all the Charts (use on a eprate page)
*/

function addPlotsAndRanksToEpRatePage() {
  var seasonData = collectDataPointsBySeasonAndAddRanks();
  plotData = getPlotData(seasonData);
  n = plotData.num;
  width = Math.max(300, n * 30);
  addGlobalStyle('.statsHeading { margin-left:10px !important; margin-bottom:10px !important; }');
  addGlobalStyle('.statsDiv { float:left; max-width:100%; margin-top:10px; width:'.concat(width).concat('px;}'));
  addGlobalStyle('#seasonAverage { margin-top: -3px; max-width:100%; width:'.concat(width).concat('px;}'));
  addGlobalStyle('#seasonBoxPlot { margin-left: -10px; }');
  addGlobalStyle('#statisticsClear { clear:both; margin-bottom: 10px; }');
  //addGlobalStyle('#root td.seasonBest { color: gold; background-color: #eeeeee;}');
  //addGlobalStyle('#root td.seasonSecond { color: silver;}');
  //addGlobalStyle('#root td.seasonThird { color: #CD7F32;}');
  addGlobalStyle('#root td.seasonBest { background-color: gold;}');
  addGlobalStyle('#root td.seasonSecond { background-color: silver;}');
  addGlobalStyle('#root td.seasonThird { background-color: #CD7F32;}');
  addGlobalStyle('#root td.seasonWorst { background-color: red;}');
  
  clearDivContent='';
  if (plotData.specials) {
    clearDivContent='(S = Specials)';
  }
  $('#tn15adrhs').css('display', 'none');
  var statisticsHtml = $('<div style="overflow:hidden;">\
                            <h4>Season Statistics</h4>\
                            <div class="statsDiv">\
                              <h5 class="statsHeading">Rating Averages</h5>\
                              <canvas id="seasonAverage" height=190 width='.concat(width).concat('></canvas>\
                            </div>\
                            <div class="statsDiv">\
                              <h5 class="statsHeading">Rating Box Plots</h5>\
                              <div id="seasonBoxPlot"></div>\
                            </div>\
                            <div id="statisticsClear">').concat(clearDivContent).concat('</div>\
                          </div>'
  ));
  $('div#tn15content h4').eq(0).before(statisticsHtml);
  plotChart('#seasonAverage', plotData);
  plotBoxPlot('#seasonBoxPlot', plotData, width);
}
/*
Add a Link to a regular page, linking to the eprate page
if it is a TV-Show pages
*/

function addLinkToTVShowPage(linkDest) {
  episodesHeadline = $('#main_bottom .article h2');
  if ((episodesHeadline != undefined) && (episodesHeadline.eq(0).text() == 'Episodes')) {
    // this is a TV show
    $('#overview-top .star-box-details').eq(0).append('<br/><a href=\''.concat(linkDest).concat('eprate\'>Show Episode Ranking</a>'));
  }
}

function addLinkToTVShowPage(linkDest) {
  episodesHeadline = $('#main_bottom .article h2');
  if ((episodesHeadline != undefined) && (episodesHeadline.eq(0).text() == 'Episodes')) {
    // this is a TV show
    ratingBox=$('#overview-top .star-box-details');
    if (ratingBox.length == 0) {
        ratingBox=$('.ratings_wrapper');
    }
    ratingBox.eq(0).append('<br/><a href=\''.concat(linkDest).concat('eprate\'>Show Episode Ranking</a>'));
  }
}
/*
Add CSS
*/

function addGlobalStyle(css) {
  var head,
  style;
  head = document.getElementsByTagName('head') [0];
  if (!head) {
    return;
  }
  style = document.createElement('style');
  style.type = 'text/css';
  style.innerHTML = css;
  head.appendChild(style);
}
/*
Run on every matching imdb page
*/

currURL = document.URL.split('?') [0];
if (currURL.match(/eprate/g) != undefined) {
  // we are on an eprate page
  addPlotsAndRanksToEpRatePage();
} else {
  addLinkToTVShowPage(currURL);
}