RedditChart

Some data analysis for reddit

  1. // ==UserScript==
  2. // @name RedditChart
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.2
  5. // @description Some data analysis for reddit
  6. // @author wpatter6
  7. // @match *://*.reddit.com/*
  8. // @grant none
  9. // @require https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
  10. // @require https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js
  11. // ==/UserScript==
  12.  
  13. (()=>{
  14. 'use strict';
  15. const main=()=>{
  16. (function($) {
  17. let doLog=false,_canvasBoxSize = 290, _canvasMinSize = 60,_lastData=null, _lastColors=null,_noCollapse=!!localStorage.getItem("rc-noCollapse"),selectOpened=false,
  18. url = String(location), old = url.indexOf("old.reddit.com") !== -1, postSelector = old ? ".thing" : ".scrollerItem",
  19. aggregates={
  20. "Post Count":1
  21. ,"Sum of Votes":2
  22. //,"Avg Votes/Post":3//uncomment for additional aggregates for those smart enough to be here
  23. //,"Avg Votes/Post/Minute":4//Won't work in redesign
  24. },
  25. log=(a,b)=>{//log shortener
  26. if(!doLog) return;
  27. if(typeof(a)==="string") a="RedditChart:" + a;
  28. else if (!b){b=a; a="RedditChart:debug";}
  29. if(typeof(b)!=="undefined") return console.log(a,b);
  30. return console.log(a);
  31. },
  32. go=()=>{//method executed once Chart.js has loaded
  33. if(url.indexOf("reddit.com/r/")>-1 && url.indexOf("reddit.com/r/all") === -1 && url.indexOf("reddit.com/r/popular") === -1)
  34. return log("Inside subreddit. Initialization cancelled.");
  35.  
  36. log("Chart.js namespace found. Initializing...");
  37. var chart,aggregate=parseInt(localStorage.getItem("rc-agg")||"1"),exclude,
  38. curpage=execReg(/\/r\/([^\/]*)/,location),
  39. subpage=(()=>{
  40. var ret = $("div.menuarea div.dropdown.lightdrop>span").text(),
  41. regstr = location.host+"/"+(curpage?"r/"+curpage+"/":"")+"([^/?#]*)";
  42.  
  43. if(ret==="") return execReg(regstr,location);
  44. return execReg(regstr,location) + " " + ret;
  45. })(),
  46. //dom element creation
  47. el=$("<canvas>").addClass("rc-chart").attr({width:(_noCollapse?_canvasBoxSize:_canvasMinSize)+"px",height:(_noCollapse?_canvasBoxSize:_canvasMinSize)+"px",oncontextmenu:"return false;"})
  48. .mousemove((e)=>{
  49. var activePoints=chart.getElementsAtEvent(e.originalEvent),
  50. sub=activePoints.length>0?chart.data.labels[activePoints[0]._index]:null;
  51. log("sub", sub);
  52. $(postSelector).removeClass("rc-active").css("border-left","");
  53. if(sub){
  54. var highlight;
  55.  
  56. if(old) {
  57. highlight = $(postSelector + "[data-subreddit="+sub+"]");
  58. } else {
  59. highlight = $(postSelector).filter(function() {
  60. var href = $(this).find("[data-click-id='subreddit']").attr("href");
  61. return href && href.replace("/r/", "").replace("/", "") === sub;
  62. });
  63. }
  64. highlight.addClass("rc-active").css("border-left","5px solid "+_lastColors[activePoints[0]._index]);
  65. }
  66. })
  67. .mouseout(()=>{$(postSelector).removeClass("rc-active");})
  68. .mousedown((e)=>{if(e.button !== 2) return true; var activePoints=chart.getElementsAtEvent(e.originalEvent), sub=activePoints.length > 0 ? chart.data.labels[activePoints[0]._index] :null; exclude=sub; calculate(); return false;}),
  69. dd=$("<select>").addClass("rc-dd").change((e)=>{localStorage.setItem("rc-agg",aggregate=parseInt($(e.target).val())); calculate();}).click(()=>{selectOpened=!selectOpened}),
  70. ddspan=$("<span>").addClass("rc-aggspan").append(dd).append($("<span>").text((()=>{//set the text of which page we're on
  71. if(subpage && subpage.indexOf("user")===0) {//handles user multis as well
  72. var username=execReg("/user/([^?#]*)",location);
  73. if(username) return subpage.replace("user", username);
  74. }
  75. var def = $(".user").text()==="Want to join? Log in or sign up in seconds." ? "popular" : "front page";
  76. return (curpage?"/r/"+curpage:def) + " " + subpage;
  77. })()).css({paddingLeft:"10px"})).css({padding:"10px"}),
  78. div=$("<div>").addClass("rc-float").appendTo($("body")).append(ddspan).append(el).hover(()=>{
  79. ddspan.show();
  80. dochart(null, _canvasBoxSize, true);
  81. }, ()=>{
  82. if(!selectOpened){
  83. ddspan.hide();
  84. dochart(null, _canvasMinSize, true);
  85. }
  86. }),
  87. calculate=()=>{//calculate and set up the chart
  88. log("calculating...");
  89. var aggregateData=[],namecount={},rawdata=getRawData();
  90. log("raw data:", rawdata);
  91. for(var i=0; i<rawdata.length; i++){
  92. var sub=rawdata[i].sub;
  93. if(!sub||sub===exclude) continue;
  94. var idx=((d,s)=>{ for(var j=0; j<d.length; j++) if(d[j].sub.toLowerCase()===s) return j; return -1;})(aggregateData,sub.toLowerCase()),
  95. curval=idx+1?aggregateData[idx].value:0;
  96. switch(aggregate){
  97. case 1: curval++; break;
  98. case 2:case 3: curval+=rawdata[i].votes; break;
  99. case 4: curval+=rawdata[i].vpm; break;
  100. }
  101. if(idx+1){
  102. aggregateData[idx].value=curval;
  103. namecount[sub]++;
  104. }else{
  105. aggregateData.push({sub:rawdata[i].sub,value:curval});
  106. namecount[sub]=1;
  107. }
  108. }
  109. if([3,4].indexOf(aggregate)+1)//averages need to be divided by post count
  110. for(var j=0; j<aggregateData.length; j++){
  111. var agg=aggregateData[j];
  112. agg.value=Math.round(agg.value/namecount[agg.sub]);
  113. }
  114.  
  115. //sort from biggest to smallest
  116. aggregateData.sort((a,b)=>{
  117. if(b.value-a.value===0) return a.sub.localeCompare(b.sub);
  118. return b.value - a.value;
  119. });
  120. log("calculating complete.");
  121. dochart(aggregateData);
  122. return aggregateData;
  123. },
  124. dochart=(data, boxSize, noAnimate)=>{//take aggregated/sorted data and apply it to the chart & .subreddit elements
  125. log("charting...");
  126. var reuseColors = false;
  127. if(!data){
  128. if(!_lastData){
  129. return calculate;
  130. }
  131. data = _lastData;
  132. reuseColors = true;
  133. } else _lastData = data;
  134.  
  135. var names=[],dataitems=[],colors= reuseColors ? _lastColors : getRandomColors(data.length);
  136. _lastColors = colors;
  137. for(var i=0; i<data.length; i++){
  138. var agg=data[i];
  139. names.push(agg.sub);
  140. dataitems.push(agg.value);
  141. }
  142.  
  143. var subEls;
  144.  
  145. if(old) {
  146. subEls = $(".subreddit:visible");
  147. } else {
  148. subEls = $("div[id^='SubredditInfoTooltip']").siblings("a[data-click-id='subreddit']");
  149. }
  150.  
  151. log("dochart", subEls.length);
  152.  
  153. subEls.each((i,e)=>{
  154. var sub =$(e).text().replace(/\/?r\//gi,"");
  155. if(sub===exclude){$(e).css({backgroundColor:"",color:""});return;}
  156. var idx=names.indexOf(sub);if(idx===-1) return;
  157. var color=colors[idx],fontColor=getColorLuma(color)<70?"white":"black";
  158. $(e).css({backgroundColor:color,color:fontColor,borderRadius:'3px',padding:'0 3px'});
  159. });
  160.  
  161. if(chart) chart.destroy();
  162. if(boxSize && boxSize > 0){
  163. var context = el[0].getContext("2d");
  164. context.canvas.width = boxSize;
  165. context.canvas.height = boxSize;
  166. }
  167. chart=new Chart(el,{//Chart.js initialize with data
  168. type:"pie",
  169. data:{
  170. labels:names,
  171. datasets:[{data:dataitems,backgroundColor:colors,borderColor:colors}]
  172. },
  173. options:{
  174. responsive:true,
  175. animation:{duration: noAnimate?0:1000},
  176. legend:{display:false},
  177. onClick:(e)=>{
  178. var activePoints=chart.getElementsAtEvent(e),sub=activePoints[0]?chart.data.labels[activePoints[0]._index]:null;
  179. if(!sub) return; open(location.protocol+"//"+location.hostname+"/r/"+sub);
  180. }
  181. }
  182. });
  183. chart.update();
  184. log("chart complete.");
  185. return data;
  186. },
  187. getRawData=()=>{
  188. return old ? $(postSelector+":visible:not(.promoted)").map((i,e)=>{
  189. let diff=(Date.now()-new Date($(e).find("time").attr("datetime")).getTime())/60000,
  190. votes=parseInt($(e).find(".score.unvoted").attr("title"));
  191. return {
  192. sub:$(e).attr("data-subreddit"),
  193. votes:votes,
  194. vpm:Math.round(votes/diff)};
  195. }).get()
  196. : $(postSelector+".Post:not(.promotedlink)").map((i, e) => {
  197. let voteString = $(e).find("button[id^='upvote-button']").siblings("div").first().text(),
  198. votes = voteString.indexOf("k") !== -1 ? parseInt(parseFloat(voteString) * 1000) : parseInt(voteString);
  199. return {
  200. sub: $(e).find("[data-click-id='subreddit']").attr("href").replace("/r/", "").replace("/", ""),
  201. votes:votes
  202. }
  203. }).get()
  204. };
  205.  
  206. if(old) {
  207. //triggered when Never ending reddit fires (RES)
  208. document.body.addEventListener("DOMNodeInserted",e=>{if(e.target.tagName==="DIV" && ($(e.target).attr("id")||"").indexOf("siteTable")>-1)calculate();},true);
  209. } else {
  210. let scrolled = false, scrollLoc = $(window).scrollTop();
  211.  
  212. window.addEventListener('scroll', e => {
  213. if(scrollLoc < $(window).scrollTop()){
  214. scrolled = true;
  215. scrollLoc = $(window).scrollTop();
  216. }
  217. }, false);
  218.  
  219. setInterval(() => {
  220. if(scrolled) {
  221. calculate();
  222. scrolled = false;
  223. }
  224. }, 500);
  225. }
  226. //finish setting up dropdown
  227. for(var i in aggregates) dd.append($("<option>").text(i).attr("value",aggregates[i]));
  228. dd.val(aggregate);
  229.  
  230. calculate();
  231. log("Initialization complete.");
  232. },
  233. getRandomColors=(c)=>{//get array of random colors, c = length of result array
  234. var ret=[]; for(var i=0;i<c;i++)ret.push("#000000".replace(/0/g,()=>{return (~~(Math.random()*16)).toString(16);})); return ret;
  235. },
  236. getColorLuma=(c)=>{//get color light/darkness
  237. if(c.indexOf("#")===0)c=c.substring(1); var rgb=parseInt(c,16),r=(rgb >> 16) & 0xff,g=(rgb >> 8) & 0xff,b=(rgb >> 0) & 0xff; return 0.2126 * r + 0.7152 * g + 0.0722 * b;
  238. },
  239. checkForChartJs=(c)=>{//recurring setTimeout check to wait for Chart.js to load into page. Dies after 100 loops.
  240. if(c<100){ log("Checking for chart.js namespace ("+c+")..."); if(!window.Chart) return setTimeout(checkForChartJs,50,++c); go(); } else log("Chart.js namespace not found! Cannot initialize.");
  241. },
  242. execReg=(reg, str)=>{
  243. if(typeof(reg)==="string") reg = new RegExp(reg); return (reg.exec(str)||[0,null])[1];
  244. };
  245.  
  246. checkForChartJs(0);
  247. })(jQuery.noConflict());
  248. };
  249.  
  250.  
  251. let style = document.createElement("style");
  252. style.innerHTML = ".rc-float{position:fixed;bottom:20px;right:0px; z-index:2000;border-radius:20px;padding:10px;opacity:0.4;text-align:center;} .rc-float:hover{background-color:rgba(0,0,0,0.8);opacity:1} .rc-aggspan{display:none;color:white;font-size:small;} .rc-chart{cursor:pointer;} .subreddit{border-radius:2px;padding:2px;} .rc-active{background-color:rgba(66,66,66,0.3) !important;} .rcf{height:0px;position:fixed;border:0px} .rcx { }";
  253.  
  254. let script = document.createElement("script");
  255. script.innerHTML = "("+main.toString()+")();";
  256.  
  257. document.body.appendChild(style);
  258. document.body.appendChild(script);
  259. })();