RedditChart

Some data analysis for reddit

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         RedditChart
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  Some data analysis for reddit
// @author       wpatter6
// @match        *://*.reddit.com/*
// @grant        none
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.min.js
// ==/UserScript==

(()=>{
    'use strict';
    const main=()=>{
        (function($) {
            let doLog=false,_canvasBoxSize = 290, _canvasMinSize = 60,_lastData=null, _lastColors=null,_noCollapse=!!localStorage.getItem("rc-noCollapse"),selectOpened=false,
            url = String(location), old = url.indexOf("old.reddit.com") !== -1, postSelector = old ? ".thing" : ".scrollerItem",
            aggregates={
                "Post Count":1
                ,"Sum of Votes":2
                //,"Avg Votes/Post":3//uncomment for additional aggregates for those smart enough to be here
                //,"Avg Votes/Post/Minute":4//Won't work in redesign
            },
            log=(a,b)=>{//log shortener
                if(!doLog) return;
                if(typeof(a)==="string") a="RedditChart:" + a;
                else if (!b){b=a; a="RedditChart:debug";}
                if(typeof(b)!=="undefined") return console.log(a,b);
                return console.log(a);
            },
            go=()=>{//method executed once Chart.js has loaded
                if(url.indexOf("reddit.com/r/")>-1 && url.indexOf("reddit.com/r/all") === -1 && url.indexOf("reddit.com/r/popular") === -1)
                    return log("Inside subreddit. Initialization cancelled.");

                log("Chart.js namespace found.  Initializing...");
                var chart,aggregate=parseInt(localStorage.getItem("rc-agg")||"1"),exclude,
                curpage=execReg(/\/r\/([^\/]*)/,location),
                subpage=(()=>{
                    var ret = $("div.menuarea div.dropdown.lightdrop>span").text(),
                        regstr = location.host+"/"+(curpage?"r/"+curpage+"/":"")+"([^/?#]*)";

                    if(ret==="") return execReg(regstr,location);
                    return execReg(regstr,location) + " " + ret;
                })(),
                //dom element creation
                el=$("<canvas>").addClass("rc-chart").attr({width:(_noCollapse?_canvasBoxSize:_canvasMinSize)+"px",height:(_noCollapse?_canvasBoxSize:_canvasMinSize)+"px",oncontextmenu:"return false;"})
                    .mousemove((e)=>{
                        var activePoints=chart.getElementsAtEvent(e.originalEvent),
                            sub=activePoints.length>0?chart.data.labels[activePoints[0]._index]:null; 
                        
                        log("sub", sub);
                        
                        $(postSelector).removeClass("rc-active").css("border-left",""); 
                        
                        if(sub){
                            var highlight;

                            if(old) {
                                highlight = $(postSelector + "[data-subreddit="+sub+"]");
                            } else {
                                highlight = $(postSelector).filter(function() {
                                    var href = $(this).find("[data-click-id='subreddit']").attr("href");
                                    return href && href.replace("/r/", "").replace("/", "") === sub;
                                });
                            }
                            
                            highlight.addClass("rc-active").css("border-left","5px solid "+_lastColors[activePoints[0]._index]);
                        }
                    })
                    .mouseout(()=>{$(postSelector).removeClass("rc-active");})
                    .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;}),
                dd=$("<select>").addClass("rc-dd").change((e)=>{localStorage.setItem("rc-agg",aggregate=parseInt($(e.target).val())); calculate();}).click(()=>{selectOpened=!selectOpened}),
                ddspan=$("<span>").addClass("rc-aggspan").append(dd).append($("<span>").text((()=>{//set the text of which page we're on
                    if(subpage && subpage.indexOf("user")===0) {//handles user multis as well
                        var username=execReg("/user/([^?#]*)",location);
                        if(username) return subpage.replace("user", username);
                    }
                    var def = $(".user").text()==="Want to join? Log in or sign up in seconds." ? "popular" : "front page";
                    return (curpage?"/r/"+curpage:def) + " " + subpage;
                })()).css({paddingLeft:"10px"})).css({padding:"10px"}),
                div=$("<div>").addClass("rc-float").appendTo($("body")).append(ddspan).append(el).hover(()=>{
                    ddspan.show();
                    dochart(null, _canvasBoxSize, true);
                }, ()=>{
                    if(!selectOpened){
                        ddspan.hide();
                        dochart(null, _canvasMinSize, true);
                    }
                }),
                calculate=()=>{//calculate and set up the chart
                    log("calculating...");
                    var aggregateData=[],namecount={},rawdata=getRawData();
                    log("raw data:", rawdata);
                    for(var i=0; i<rawdata.length; i++){
                        var sub=rawdata[i].sub;
                        if(!sub||sub===exclude) continue;
                        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()),
                            curval=idx+1?aggregateData[idx].value:0;
                        switch(aggregate){
                            case 1: curval++; break;
                            case 2:case 3: curval+=rawdata[i].votes; break;
                            case 4: curval+=rawdata[i].vpm; break;
                        }
                        if(idx+1){
                            aggregateData[idx].value=curval;
                            namecount[sub]++;
                        }else{
                            aggregateData.push({sub:rawdata[i].sub,value:curval});
                            namecount[sub]=1;
                        }
                    }
                    if([3,4].indexOf(aggregate)+1)//averages need to be divided by post count
                        for(var j=0; j<aggregateData.length; j++){
                            var agg=aggregateData[j];
                            agg.value=Math.round(agg.value/namecount[agg.sub]);
                        }

                    //sort from biggest to smallest
                    aggregateData.sort((a,b)=>{
                        if(b.value-a.value===0) return a.sub.localeCompare(b.sub);
                        return b.value - a.value;
                    });
                    log("calculating complete.");
                    dochart(aggregateData);
                    return aggregateData;
                },
                dochart=(data, boxSize, noAnimate)=>{//take aggregated/sorted data and apply it to the chart & .subreddit elements
                    log("charting...");
                    var reuseColors = false;
                    if(!data){
                        if(!_lastData){
                            return calculate;
                        }
                        data = _lastData;
                        reuseColors = true;
                    } else _lastData = data;

                    var names=[],dataitems=[],colors= reuseColors ? _lastColors : getRandomColors(data.length);
                    _lastColors = colors;
                    for(var i=0; i<data.length; i++){
                        var agg=data[i];
                        names.push(agg.sub);
                        dataitems.push(agg.value);
                    }

                    var subEls;

                    if(old) {
                        subEls = $(".subreddit:visible");
                    } else {
                        subEls = $("div[id^='SubredditInfoTooltip']").siblings("a[data-click-id='subreddit']");
                    }

                    log("dochart", subEls.length);

                    subEls.each((i,e)=>{
                        var sub =$(e).text().replace(/\/?r\//gi,"");
                        if(sub===exclude){$(e).css({backgroundColor:"",color:""});return;}
                        var idx=names.indexOf(sub);if(idx===-1) return;
                        var color=colors[idx],fontColor=getColorLuma(color)<70?"white":"black";
                        $(e).css({backgroundColor:color,color:fontColor,borderRadius:'3px',padding:'0 3px'});
                    });

                    if(chart) chart.destroy();
                    if(boxSize && boxSize > 0){
                        var context = el[0].getContext("2d");
                        context.canvas.width = boxSize;
                        context.canvas.height = boxSize;
                    }
                    chart=new Chart(el,{//Chart.js initialize with data
                        type:"pie",
                        data:{
                            labels:names,
                            datasets:[{data:dataitems,backgroundColor:colors,borderColor:colors}]
                        },
                        options:{
                            responsive:true,
                            animation:{duration: noAnimate?0:1000},
                            legend:{display:false},
                            onClick:(e)=>{
                                var activePoints=chart.getElementsAtEvent(e),sub=activePoints[0]?chart.data.labels[activePoints[0]._index]:null;
                                if(!sub) return; open(location.protocol+"//"+location.hostname+"/r/"+sub);
                            }
                        }
                    });
                    chart.update();
                    log("chart complete.");
                    return data;
                },
                getRawData=()=>{
                    return old ? $(postSelector+":visible:not(.promoted)").map((i,e)=>{
                        let diff=(Date.now()-new Date($(e).find("time").attr("datetime")).getTime())/60000,
                            votes=parseInt($(e).find(".score.unvoted").attr("title"));
                        return {
                            sub:$(e).attr("data-subreddit"),
                            votes:votes,
                            vpm:Math.round(votes/diff)};
                    }).get()
                    : $(postSelector+".Post:not(.promotedlink)").map((i, e) => {
                        let voteString = $(e).find("button[id^='upvote-button']").siblings("div").first().text(),
                            votes = voteString.indexOf("k") !== -1 ? parseInt(parseFloat(voteString) * 1000) : parseInt(voteString);
                        return {
                            sub: $(e).find("[data-click-id='subreddit']").attr("href").replace("/r/", "").replace("/", ""),
                            votes:votes
                        }
                    }).get()
                };

                if(old) {
                    //triggered when Never ending reddit fires (RES)
                    document.body.addEventListener("DOMNodeInserted",e=>{if(e.target.tagName==="DIV" && ($(e.target).attr("id")||"").indexOf("siteTable")>-1)calculate();},true);
                } else {
                    let scrolled = false, scrollLoc = $(window).scrollTop();

                    window.addEventListener('scroll', e => {
                        if(scrollLoc < $(window).scrollTop()){
                            scrolled = true;
                            scrollLoc = $(window).scrollTop();
                        }
                    }, false);

                    setInterval(() => {
                        if(scrolled) {
                            calculate();
                            scrolled = false;
                        }
                    }, 500);
                }
                //finish setting up dropdown
                for(var i in aggregates) dd.append($("<option>").text(i).attr("value",aggregates[i]));
                dd.val(aggregate);

                calculate();
                log("Initialization complete.");
            },
            getRandomColors=(c)=>{//get array of random colors, c = length of result array
                var ret=[]; for(var i=0;i<c;i++)ret.push("#000000".replace(/0/g,()=>{return (~~(Math.random()*16)).toString(16);})); return ret;
            },
            getColorLuma=(c)=>{//get color light/darkness
                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;
            },
            checkForChartJs=(c)=>{//recurring setTimeout check to wait for Chart.js to load into page.  Dies after 100 loops.
                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.");
            },
            execReg=(reg, str)=>{
                if(typeof(reg)==="string") reg = new RegExp(reg); return (reg.exec(str)||[0,null])[1];
            };

            checkForChartJs(0);
        })(jQuery.noConflict());
    };


    let style = document.createElement("style");
    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 { }";

    let script = document.createElement("script");
    script.innerHTML = "("+main.toString()+")();";

    document.body.appendChild(style);
    document.body.appendChild(script);
})();