MTurk Status Page Chart

Adds some, hopefully slightly useful, eyecandy to your status page

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        MTurk Status Page Chart
// @namespace   localhost
// @author      ThirdClassInternationalMasterTurker
// @description Adds some, hopefully slightly useful, eyecandy to your status page
// @include     https://www.mturk.com/mturk/status
// @version     1.6
// @require     http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js
// @require     http://code.highcharts.com/highcharts.js
// @grant       GM_getValue
// @grant       GM_setValue
// ==/UserScript==

//
// 2012-09-07 First public release by ThirdClassInternationalMasterTurker
// 
// 2012-09-09 Version 1.1 Minor fix to make it work on both pages (when over 30 days)
//                    1.2 Another fix, @included too many pages
//
// 2012-09-14 Version 1.3 Added support for time tracking statistics with MTurk Time Tracker script
//
// 2012-09-19 Version 1.4 You can modify work time by clicking it on status page
//
// 2012-12-02 Version 1.5: Added @downloadURL and @updateURL
//
// 2012-12-12 Version 1.6: Added options dialog
//

// --- DEFAULT SETTINGS ------------------------------------------------ //
// Set your own target earnings here
var EARNINGS_OK   = 10;
var EARNINGS_HIGH = 20;

// Change background colour or font colour (true/false)
var COLOUR_ROWS = true;
// Apply colours to rows with pending hits (true/false)
var COLOUR_PENDING_ROWS = false;

// Make quick links for rejected and pending hits (true/false)
var LINK_REJECTED = true;
var LINK_PENDINGS = true;

// Background colours (no effect if COLOUR_ROW is false)
var COLOUR_HIGH_ODD  = '#44DD44';
var COLOUR_HIGH_EVEN = '#88EE88';
var COLOUR_OK_ODD    = '#f1f3eb';
var COLOUR_OK_EVEN   = '#FFFFFF';
var COLOUR_LOW_ODD   = '#FF5555';
var COLOUR_LOW_EVEN  = '#FF8888';

var COLOUR_PENDING_ODD  = '#FFFF66';
var COLOUR_PENDING_EVEN = '#FFFFAA';


// Font colours (no effect if COLOUR_ROW is true)
var COLOUR_PENDING     = '#FFFF66';
var COLOUR_REJECTED    = '#FF0000';
var COLOUR_APPROVED    = 'green';
var COLOUR_EARNINGS    = 'orange';
var COLOUR_HOURLY_RATE = 'black';
var COLOUR_LOW         = '#FF0000';
var COLOUR_OK          = '#000000';
var COLOUR_HIGH        = '#008000';

// Set this false if you don't want borders around rejects
// '2px solid red' adds red borders
var BORDER_STYLE_REJECTED = false; //'2px solid red';

var DRAW_BAR_CHART = true;
// -------------------------------------------------------------------- //
var ROWS = [];

if (typeof GM_getValue !== 'undefined')
{
  var CONFIG_DIALOG = null;

  if (GM_getValue('EARNINGS_OK') !== undefined)
    EARNINGS_OK = GM_getValue('EARNINGS_OK');
  if (GM_getValue('EARNINGS_HIGH') !== undefined)
    EARNINGS_HIGH = GM_getValue('EARNINGS_HIGH');
  if (GM_getValue('DRAW_BAR_CHART') !== undefined)
    DRAW_BAR_CHART = GM_getValue('DRAW_BAR_CHART');

    

  function config_dialog_close_func(save, inputs)
  {
    return function()
    {
      if (save == false)
      {
        inputs[0].value = EARNINGS_OK;
        inputs[1].value = EARNINGS_HIGH;
        if (DRAW_BAR_CHART == true)
          inputs[2].checked = true;     
      }
      else
      {
        var error = false;
        try {
          EARNINGS_OK = parseInt(inputs[0].value);
          EARNINGS_HIGH = parseInt(inputs[1].value);
          DRAW_BAR_CHART = inputs[2].checked == true;
          GM_setValue('EARNINGS_OK', EARNINGS_OK);
          GM_setValue('EARNINGS_HIGH', EARNINGS_HIGH);
          GM_setValue('DRAW_BAR_CHART', DRAW_BAR_CHART);
          if (EARNINGS_HIGH <= EARNINGS_OK)
            error = true;
        }
        catch(err)
        {
          error = true;
        }
        if (error)
          return;
        
        for (var i=0; i<ROWS.length; i++)
        {
          var row = ROWS[i];
          if (row.pending == 0 && row.earnings < EARNINGS_OK)
            row.element.style.backgroundColor = (i%2 == 1) ? COLOUR_LOW_ODD : COLOUR_LOW_EVEN;
          else if (row.pending == 0 && row.earnings > EARNINGS_HIGH)
            row.element.style.backgroundColor = (i%2 == 1) ? COLOUR_HIGH_ODD : COLOUR_HIGH_EVEN;
          else if (row.pending == 0)
            row.element.style.backgroundColor = (i%2 == 1) ? COLOUR_OK_ODD : COLOUR_OK_EVEN;
        }
      }
      CONFIG_DIALOG.style.display = 'none';
    };  
} 

  function config_dialog()
  {
    if (CONFIG_DIALOG == null)
    {
      CONFIG_DIALOG = document.createElement('div');
      CONFIG_DIALOG.style.display = 'block';

      CONFIG_DIALOG.style.position = 'fixed';
      CONFIG_DIALOG.style.width = '500px';
      CONFIG_DIALOG.style.height = '100px';
      CONFIG_DIALOG.style.left = '50%';
      CONFIG_DIALOG.style.right = '50%';
      CONFIG_DIALOG.style.margin = '-250px auto auto -250px';
      CONFIG_DIALOG.style.top = '400';
      CONFIG_DIALOG.style.padding = '10px';
      CONFIG_DIALOG.style.border = '2px';
      CONFIG_DIALOG.style.textAlign = 'center';
      CONFIG_DIALOG.style.verticalAlign = 'middle';
      CONFIG_DIALOG.style.borderStyle = 'solid';
      CONFIG_DIALOG.style.borderColor = 'black';
      CONFIG_DIALOG.style.backgroundColor = 'white';
      CONFIG_DIALOG.style.color = 'black';
      CONFIG_DIALOG.style.zIndex = '100';

      var inputs = [];
      inputs[0] = document.createElement('input');
      inputs[1] = document.createElement('input');
      inputs[2] = document.createElement('input');
      var label0 = document.createElement('label');
      var label1 = document.createElement('label');
      var label2 = document.createElement('label');

      label0.textContent = 'Earnings OK: $';
      label1.textContent = 'Earnings High: $';
      label2.textContent = 'Draw Bar Chart: ';
  
      inputs[0].maxLength = '3';
      inputs[0].size = '3';
      inputs[0].defaultValue = EARNINGS_OK;
      inputs[1].maxLength = '3';
      inputs[1].size = '3';
      inputs[1].defaultValue = EARNINGS_HIGH;
      inputs[2].setAttribute('type', 'checkbox');
      if (DRAW_BAR_CHART == true)
        inputs[2].checked = true;
      else
        inputs[2].checked = false;
      label0.title = inputs[0].title = 'If earnings for a day is less than this, day is coloured red';
      label1.title = inputs[1].title = 'If earnings for a day is more than this, day is coloured green';
      label2.title = inputs[2].title = 'Draw Bar Chart at the bottom of the page';

      var save_button = document.createElement('button');
      save_button.textContent = 'Save';
      save_button.addEventListener("click", config_dialog_close_func(true, inputs), false);
      save_button.style.margin = '5px';
      var cancel_button = document.createElement('button');
      cancel_button.textContent = 'Cancel';
      cancel_button.addEventListener("click", config_dialog_close_func(false, inputs), false);
      cancel_button.style.margin = '5px';

      CONFIG_DIALOG.appendChild(label0);
      CONFIG_DIALOG.appendChild(inputs[0]);
      CONFIG_DIALOG.appendChild(document.createElement('br'));
      CONFIG_DIALOG.appendChild(label1);
      CONFIG_DIALOG.appendChild(inputs[1]);
      CONFIG_DIALOG.appendChild(document.createElement('br'));
      CONFIG_DIALOG.appendChild(label2);
      CONFIG_DIALOG.appendChild(inputs[2]);
      CONFIG_DIALOG.appendChild(document.createElement('br'));
      CONFIG_DIALOG.appendChild(cancel_button);
      CONFIG_DIALOG.appendChild(save_button);
      document.body.appendChild(CONFIG_DIALOG);
    }
    else
    {
      CONFIG_DIALOG.style.display = 'block';
    }  
  }

  function config_func()
  {
    return function()
    {
      config_dialog();
    };  
  }

  var config_button = document.createElement('button');
  var title = document.getElementsByClassName('IKnowYou')[0];
  config_button.textContent = 'Status Page Options';
  config_button.addEventListener("click", config_func(), false);
  title.appendChild(config_button);
}
/* ----------------------------------------------------------------------------------- */

function formatTime(msec) {
    if (isNaN(msec))
      return "-";
    var seconds = Math.floor(msec / 1000) % 60;
    var minutes = Math.floor((msec / 1000) / 60) % 60;
    var hours = Math.floor(((msec / 1000) / 60) / 60) % 24;

    if (hours > 0)
      seconds = "";
    else
      seconds = "" + seconds + "s";
    minutes == 0 ? minutes = "" : minutes = "" + minutes + "m ";
    hours == 0   ? hours = "" : hours = "" + hours + "h ";

    return hours + minutes + seconds;
}

function formatHourlyRate(dollars, msec, formatForTable) {
  if (dollars == 0 || isNaN(msec))  {
    if (formatForTable)
      return "-";
    return 0;
  }
  var rate = (dollars/(msec/1000/60/60));

  if (formatForTable)
    return "$" + rate.toFixed(2);
  return rate;
}

function change_time(date) {
  return function() {
    oldTime = localStorage[date];
    newTime = prompt('Time worked on ' + date + ' (H:MM)\nInsert - to empty time\nReload page to see all changes', "");

    if (newTime != null && newTime != "") {
      if (newTime.match("^-$")) {
        localStorage.removeItem(date);
        document.getElementById(date).innerHTML = "-";;
      }
      else if (!newTime.match('[0-2]?[0-9]:[0-5][0-9]')) {
        alert("Invalid time!");
      }
      else {
        var t = newTime.split(':');
        hours = parseInt(t[0]);
        minutes = parseInt(t[1]);
        newTime = ((hours*60+minutes)*60000);
        if (newTime < 0 || newTime >= 86400000)
          alert("Invalid time!");
        else {
          localStorage[date] = newTime;
          document.getElementById(date).innerHTML = formatTime(newTime);
        }
      }
    }
  }
}

function clear_localstorage() {
  if (confirm("Press OK to clear all data from Local Storage!"))
    localStorage.clear();
};

function clear_cache_items() {
  if (confirm("Press OK to clear Time Tracker Cache items from Local Storage!")) {
    for (i=0; i<=localStorage.length-1; i++) {  
        key = localStorage.key(i);
        if (key.match('STATUS_'))
          localStorage.removeItem(key);
    }
  }
};

(function() {
var rows = document.evaluate('//tr[@class]',
           document,
           null,
           XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); 

var container = document.createElement('div');
var chart;
var options = {
    chart: {
        renderTo: 'container',
        margin: [100,100,100,100],
        zoomType: 'xy'
    },
    title: {
        text: ''
    },
    xAxis: [{
        categories: [],
        labels: {
            rotation: 90,
            align: 'left'
        },
    }],
           yAxis: [{ // Primary yAxis
                minorTickInterval: 'auto',
                gridLineWidth: 2,
                max: null,
                min: 0,
                labels: {
                    formatter: function() {
                        return this.value +' HITs';
                    },
                },
                title: {
                    text: 'HITs',
                }
            }, { // Secondary yAxis
                minorTickInterval: 'auto',
                gridLineWidth: 2,
                allowDecimals: true,
                max: null,
                min: 0,
                minRange: 10,
                title: {
                    text: 'Earnings',
                },
                labels: {
                    formatter: function() {
                        return '\$' + this.value;
                    },
                },
                opposite: true
            }, {
                minorTickInterval: 'auto',
                gridLineWidth: 2,
                allowDecimals: true,
                max: null,
                min: 0,
                minRange: 10,
                title: {
                    text: 'Hourly Rate',
                },
                labels: {
                    formatter: function() {
                        return '\$' + this.value + '/h';
                    },
                },
                opposite: true
            }],
    plotOptions: {
       series: {
            stacking: 'normal'
        },
        column: {
            pointPadding: 0,
            groupPadding: 0.05,
        }
    },
    series: [{
        name: 'pending',
        color: COLOUR_PENDING,
        type: 'column',
        yAxis: 0,
        data: []
    }, {
        name: 'rejected',
        color: COLOUR_REJECTED,
        type: 'column',
        yAxis: 0,
        data: []
   }, {
        name: 'approved',
        color: COLOUR_APPROVED,
        type: 'column',
        yAxis: 0,
        data: []
   }, {
        name: 'earnings',
        color: COLOUR_EARNINGS,
        type: 'spline',
        yAxis: 1,
        stack: 1,
        data: []
   }, {
        name: '$/h',
        color: COLOUR_HOURLY_RATE,
        type: 'spline',
        yAxis: 2,
        stack: 2,
        data: []
   }]
};

container.id = 'container';
container.height = 400;
container.width  = '800';

document.body.appendChild(container);

var data =[];
var data2 =[];
var data3 =[];
var data4 =[];
var data5 =[];
var data6 =[];

for (var i=0;i<rows.snapshotLength;i++) {
  var row = rows.snapshotItem(i);

  if (row.cells.length != 6)
    continue;
  if (row.className.match('grayHead')) {
    // extra columns only if using time tracker script
    if (localStorage["LOG START"] != undefined)
      row.innerHTML += "<th>Time</th><th>$/h</th>";
    continue;
  }
  if (row.className.match('odd|even') == null) {
    continue;
  }

  var odd = row.className.match('odd');
  var approved = parseInt(row.cells[2].innerHTML);
  var rejected = parseInt(row.cells[3].innerHTML);
  var pending  = parseInt(row.cells[4].innerHTML);
  var earnings = row.cells[5].childNodes[0].innerHTML;
  var dollars = parseFloat(earnings.slice(earnings.search('\\$')+1));
  var date = row.cells[0].childNodes[1].href.substr(53);

  ROWS.push({element: row, earnings: dollars, pending: pending});

  data.unshift(pending);
  data2.unshift(rejected);
  data3.unshift(approved);
  data4.unshift(dollars);

  data5.unshift(row.cells[0].textContent.replace(/, 20../, ""));

  if (pending > 0) {
    row.cells[4].style.color = COLOUR_PENDING;
  }

  if (parseInt(row.cells[3].innerHTML) > 0) {
    row.cells[3].style.color = COLOUR_REJECTED;
  }

  if (COLOUR_ROWS) {
    if (pending != 0 && !COLOUR_PENDING_ROWS) {
      if (odd)
        row.style.backgroundColor = COLOUR_PENDING_ODD;
      else
        row.style.backgroundColor = COLOUR_PENDING_EVEN;
    }


    if ((pending == 0 || COLOUR_PENDING_ROWS) && dollars >= EARNINGS_HIGH) {
      if (odd)
        row.style.backgroundColor = COLOUR_HIGH_ODD;
      else
        row.style.backgroundColor = COLOUR_HIGH_EVEN;
    }
    else if ((pending == 0 || COLOUR_PENDING_ROWS) && dollars < EARNINGS_OK) {
      if (odd)
        row.style.backgroundColor = COLOUR_LOW_ODD;
      else
        row.style.backgroundColor = COLOUR_LOW_EVEN;
    }
    else if (pending == 0 || COLOUR_PENDING_ROWS) {
      if (odd)
        row.style.backgroundColor = COLOUR_OK_ODD;
      else
        row.style.backgroundColor = COLOUR_OK_EVEN;
    }
  }
  else {
    if ((pending == 0 || COLOUR_PENDING_ROWS) && dollars < EARNINGS_OK) {
        row.cells[5].style.color = COLOUR_LOW;
    }
    else if ((pending == 0 || COLOUR_PENDING_ROWS) && dollars >= EARNINGS_HIGH) {
        row.cells[5].style.color = COLOUR_HIGH;
    }
    else if (pending == 0 || COLOUR_PENDING_ROWS) {
        row.cells[5].style.color = COLOUR_OK;
    }
  }

  if (LINK_REJECTED && rejected > 0) {
    row.cells[3].innerHTML = '<a href="' + row.cells[0].childNodes[1].href + '&sortType=Rejected">' + rejected + '</a>';
  }
  if (LINK_PENDINGS && pending > 0) {
    row.cells[4].innerHTML = '<a href="' + row.cells[0].childNodes[1].href + '&sortType=Pending">' + pending + '</a>';
  }
  if (BORDER_STYLE_REJECTED && rejected > 0) {
    row.cells[3].style.border = BORDER_STYLE_REJECTED;
  }

  // extra columns only if using time tracker script
  if (localStorage["LOG START"] != undefined) {
    row.innerHTML += "<td style=\"text-align:right\" class=\"timeWorked\" id=\"" + date + "\">" + formatTime(parseInt(localStorage[date])) + "</td>";
    row.innerHTML += "<td style=\"text-align:right\">" + formatHourlyRate(dollars, parseInt(localStorage[date]), true) + "</td>";
    data6.unshift(formatHourlyRate(dollars, parseInt(localStorage[date]), false));
  }
}
/* ----------------------------------------------------------------------------------- */

options.series[0].data = data;
options.series[1].data = data2;
options.series[2].data = data3;
options.series[3].data = data4;
options.series[4].data = data6;
options.xAxis[0].categories = data5;

if (DRAW_BAR_CHART == true)
{
  $(document).ready(function() {
        chart = new Highcharts.Chart(options);
     });
}
})();

var clear_storage_button = document.createElement('button');
var clear_cache_button = document.createElement('button');
clear_storage_button.textContent = 'Clear Local Storage';
clear_cache_button.textContent = 'Clear Time Tracker Cache';

document.body.appendChild(document.createElement('hr'));
document.body.appendChild(clear_storage_button);
document.body.appendChild(clear_cache_button);
clear_storage_button.addEventListener("click", clear_localstorage, false);
clear_cache_button.addEventListener("click", clear_cache_items, false);


if (localStorage["LOG START"] != undefined) {
  var tds = document.getElementsByClassName('timeWorked');
  for (var i=0; i<tds.length; i++) {
    var a = tds[i].id;
    tds[i].addEventListener("click", change_time(tds[i].id), false);
  }
}