sherman / Worldometer - Coronavirus: Daily percentages for cases and deaths

// ==UserScript==
// @namespace    https://openuserjs.org/users/sherman
// @name         Worldometer - Coronavirus: Daily percentages for cases and deaths
// @description  Adds daily percentages for cases and deaths
// @version      2.2.1
// @copyright    2020, sherman (https://openuserjs.org/users/sherman)
// @license      MIT
// @include      https://www.worldometers.info/coronavirus/*
// @grant        none
// @downloadURL  https://openuserjs.org/install/sherman/Worldometer_-_Coronavirus_Daily_percentages_for_cases_and_deaths.user.js
// @updateURL    https://openuserjs.org/meta/sherman/Worldometer_-_Coronavirus_Daily_percentages_for_cases_and_deaths.meta.js
// ==/UserScript==

// ==OpenUserJS==
// @author       sherman
// ==/OpenUserJS==

(function () {
  'use strict';

  const minOpactiy = 0.3;

  var opacityOverrideStyle = document.createElement("style");
  opacityOverrideStyle.innerHTML = "tr:hover td { opacity: 1 !important; }";
  document.head.appendChild(opacityOverrideStyle)

  $(".dataTable tr").map(function (_, tr) {

    let $tr = $(tr);

    var fields = [];

    var data = {
      $el: $tr,
      fields: fields,
      cases: {
        total: getColumnData(1),
        totalPerMil: getColumnData(8),

        new: getColumnData(2),
        active: getColumnData(6),
        serious: getColumnData(7)
      },
      deaths: {
        total: getColumnData(3),
        totalPerMil: getColumnData(9),

        new: getColumnData(4)
      },
      recoveries: {
        total: getColumnData(5)
      },
      tests: {
        total: getColumnData(10),
        totalPerMil: getColumnData(11)
      }
    };

    // Additional aggregated data fields
    data.cases.closed = { value: data.recoveries.total.value + data.deaths.total.value };

    return data;

    function getColumnData(colIndex) {
      var $col = $tr.find("td:eq(" + (1 + colIndex) + ")");
      var colData = {
        $el: $col,
        isCountryRow: !$tr.is(".total_row, .total_row_world"),
        value: parseElement($col)
      };

      fields.push(colData);

      return colData;
    }
  }).map(function (_, data) {

    // Field data
    data.cases.new.proportion = data.cases.new.value / (data.cases.total.value - data.cases.new.value);
    data.cases.new.prefix = "+";
    data.cases.new.maxOpacityProportion = .33;
    data.cases.new.tooltip = " new cases since yesterday";

    data.cases.active.proportion = data.cases.active.value / data.cases.total.value;
    data.cases.active.tooltop = " of total cases";

    data.cases.serious.proportion = data.cases.serious.value / data.cases.active.value;
    data.cases.serious.tooltip = " of active cases";

    data.cases.totalPerMil.proportion = data.cases.totalPerMil.value / 1000000;
    data.cases.totalPerMil.tooltip = " of overall population";
    data.cases.totalPerMil.decimals = 2;

    data.deaths.new.proportion = data.deaths.new.value / (data.deaths.total.value - data.deaths.new.value);
    data.deaths.new.prefix = "+";
    data.deaths.new.maxOpacityProportion = .33;
    data.deaths.new.tooltip = " new deaths since yesterday";

    data.deaths.total.proportion = data.deaths.total.value / data.cases.total.value;
    data.deaths.total.proportionClosed = data.deaths.total.value / data.cases.closed.value;
    data.deaths.total.tooltip = " of total cases\n" + makePercentage(data.deaths.total.proportionClosed) + " of closed cases";
    data.deaths.totalPerMil.proportion = data.deaths.totalPerMil.value / 1000000;
    data.deaths.totalPerMil.tooltip = " of overall population";
    data.deaths.totalPerMil.decimals = 2;

    data.recoveries.total.proportion = data.recoveries.total.value / data.cases.total.value;
    data.recoveries.total.proportionClosed = data.recoveries.total.value / data.cases.closed.value;
    data.recoveries.total.maxOpacityProportion = .6;
    data.recoveries.total.tooltip = " of total cases\n" + makePercentage(data.recoveries.total.proportionClosed) + " of closed cases";

    data.tests.totalPerMil.proportion = data.tests.totalPerMil.value / 1000000;
    data.tests.totalPerMil.decimals = 2;

    // Enumerate all fields
    return data.fields;
  }).each(function (_, field) {

    if (isNaN(field.proportion) || !isFinite(field.proportion)) return;

    var data = makePercentage(field.proportion, field.prefix, field.decimals);

    field.$el.css("opacity", (field.isCountryRow && field.maxOpacityProportion) ? Math.min(minOpactiy + (1 - minOpactiy) * field.proportion / field.maxOpacityProportion, 1) : 1);
    field.$el.html(field.$el.html() + "<br /><span style='font-weight: normal'>(" + data + ")</span>");
    field.$el.attr("title", field.tooltip ? data + field.tooltip : null);
  });

  function makePercentage(proportion, prefix, decimals) {
      decimals = decimals || 1;

      var roundingFactor = Math.pow(10, decimals);
      return (prefix || "") + (Math.round(proportion * 100 * roundingFactor) / roundingFactor).toString() + "%";
  }

  function parseElement($el) {
    return Number.isInteger($el) ? $el : parseInt($el.text().replace(/[,\.]/g, ""));
  }
})();