8

I appear somewhere in the middle of the "Last 30 Days" list for "Top java Answerers", but not at all on the "All time" list. I had previously appeared on the bottom of the "All time" list (I assume only for me). Is this change intentional?

I wonder if this is related to a fix for this question?

2 Answers 2

9

Unrelated.

We no longer show you unless you're in the top 20, because the "once for every user" query was too expensive, and caused lots of bad query plans to be cached.

2
  • Huh, for some reason I didn't get the "new answer posted" popup even though you posted 3 minutes before I did.
    – mmyers
    Sep 8, 2009 at 18:55
  • 11
    Is there another means (some advanced-search fu for instance) to establish how many votes a user outside the top 20 has for a tag then? Sep 8, 2009 at 19:03
28

I've implemented a Greasemonkey script to add a link for each tag in the Tags section of the user profile page. On clicking the link the votes for that tag are collated and the page is updated with the total votes. Queries are submitted one every 1.5 seconds until all pages of results have been processed.

In the screenshot below, the first "circle" shows a query in progress, the second "circle" shows a completed query.

Update: Added processing to make tags for which a badge has been awarded show the badge (inspired by this question. Also added some CSS to make the down-arrow less imposing.

Update 2: Fixed to query properly for any tags with "#", "." or "+" in them. Also added hacky hack to avoid counting votes for questions.

Update 3: Further nasty hacks to create valid css classes, and encode characters properly in searches. Any suggestions to make custom_escape less of a mess welcome

Update 4 Now handles advert images in tags

Update 5 Now uses data from the stats page to show rankings in a given tag where applicable. If the tag has more than 500 questions the number is a bit more prominent, and 1st 2nd and 3rd rankings are shown in gold, silver, and bronze.

Like this:

new screenshot

tag screenshot

The tags section modified to show badges for a certain user.

skeet tags

// ==UserScript==
// @name           so-tag-votes
// @namespace      so
// @description    show votes for a particular tag
// @include        http://stackoverflow.com/users/*
// ==/UserScript==

var cssjson = {
    ".small-multiplier":{
        "font-size":"90%",
    },
    ".vote-selector":{
        "color":"#808185",
        "font-size":"70%",
        "font-weight":"bold",
        "margin-right":"4px"
    },
    ".vote-selected":{
        "color":"#444444",
        "font-size":"110%"
    },
    ".tag-rank":{
        "color":"#808185",
        "font-size":"80%",
        "margin-right":"4px"
    },
    ".popular-tag": {
        "font-weight":"bold",
        "font-size":"90%"
    },
    ".popular-tag.tag-rank-0": {
        "color":"#E9D07C"//
    },
    ".popular-tag.tag-rank-1": {
        "color":"silver"
    },
    ".popular-tag.tag-rank-2": {
        "color":"#CD7F32"
    },
    ".vote-selected":{
        "color":"#444444",
        "font-size":"120%"
    }
}

var styleStr = "";
for(var i in cssjson){
    styleStr += i + " {\n"
    for(var j in cssjson[i]){
           styleStr += "\t" + j + ":" + cssjson[i][j] + ";\n"
    }
    styleStr += "}\n"
}

var style = window.document.createElement('style');
style.type = 'text/css';
style.innerHTML = styleStr
document.getElementsByTagName("HEAD")[0].appendChild(style);

(function(){
    function GM_init() {
        if(typeof unsafeWindow.jQuery == 'undefined') {
            window.setTimeout(GM_wait,100);
        } else {
            jQueryInit(unsafeWindow.jQuery);
        }
    }

    GM_init();

    unsafeWindow.updateVotes = function(allTagVotes) {
        window.setTimeout(function() {
            GM_setValue(allTagVotes.host+"_tag_votes", allTagVotes.toSource());
        }, 0);
    };


    function getRanking(count) {
        if(count === "") {
            return "?";
        }
        if(count < 3 || count > 19) {
            if(count %10 === 0) {
                return (count + 1) + "st";
            } else if(count %10 === 1) {
            return (count + 1) + "nd";
            } else if(count %10 === 2) {
            return (count + 1) + "rd";
            }
        }

        return (count + 1) + "th";
    }

    function jQueryInit($) {
        var userId = $("link[rel=canonical]").attr("href").split("/")[4];
        var currentUserId = $("#hlinks a[href^='/users/recent/']").attr("href").split("/")[3];

        if (currentUserId !=  userId) { return ; } //Looking at someone else's page, cancel ///

        var host = window.location.host;
        host = host.substring(0, host.indexOf('.'));

        var threshold = 20;
        var popularThreshold = 500;
        var silverThreshold = 400;

        var currentTime = new Date().getHours();

        var allTagVotes = eval(GM_getValue(host+"_tag_votes", {tagData:[], host:host}));

        var index = 0;
        $('.user-stats-table:eq(3) .post-tag').each(function() {
            $(this).next("span").after('<span class="tag-container tag-container-'+ index++ +'"></span>');
            $(this).next("span").addClass("small-multiplier");
        });

        function getTotalQuestions(data) {
            var totalQuestions = $('div.summarycount:eq(2)', data).html();
            var arr = totalQuestions.split(",");

            totalQuestions = '';
            $.each(arr, function(count, item) {
                totalQuestions  += item;
            });
            return parseInt(totalQuestions);
        }

        function getTagClass(totalQuestions, votes) {
            return ((totalQuestions > popularThreshold || votes > silverThreshold)?'popular-tag':'');
        }

        function updateTag(tagVotes, tagIndex) {
            $('.user-stats-table:eq(3) .tag-container-'+tagIndex+':first').html('<span class="tag-rank tag-rank-' + 
                tagVotes.rank + ' ' +  getTagClass(tagVotes.totalQuestions, tagVotes.votes) +
                '" style="text-align: right;" title="ranked '+getRanking(tagVotes.rank)+
                ' for all time votes in \''+tagVotes.name+'\' with '+tagVotes.votes+
                ' votes">'+tagVotes.votes+' ('+getRanking(tagVotes.rank)+')</span>');
        }

        function getTagVotes(tagIndex, tagName) {
            if(allTagVotes.length <= tagIndex ||
                !allTagVotes[tagIndex] ||
                tagName !== allTagVotes[tagIndex].name ||
                currentTime !== allTagVotes[tagIndex].lastUpdated) {

                allTagVotes[tagIndex] = {rank:"", votes:"", name:tagName, totalQuestions:"", lastUpdated:"-1"};
            }

            return allTagVotes[tagIndex];
        }


        function tagCallback(data, tagName, tagIndex) {
            var allTimeList = $('div#questions > div:eq(1) tr', data)
            for (var count=0; count < allTimeList.length; count++) {
                if(userId === $('div.user-details a', allTimeList[count]).attr("href").split("/")[2]) {
                    var tagCount = $('span.top-count:first', allTimeList[count]).html();
                    var totalQuestions = getTotalQuestions(data);

                    var tagVotes = {rank:count, votes:tagCount, name:tagName, totalQuestions:totalQuestions, lastUpdated:new Date().getHours()};

                    allTagVotes[tagIndex] = tagVotes;
        updateTag(tagVotes, tagIndex);

        unsafeWindow.updateVotes(allTagVotes);

        return;
                }
            };
        };

        //used to escape tag names for css classes
        function customEscape(tag) {
            tag = tag.replace("\.", "[").replace("\.", "[");
            tag = escape( encodeURIComponent( tag ) );
            tag = tag.replace("%", "_").replace("%", "_");                

            tag =  queryEscape(tag);
            return tag;
        }
        function queryEscape(tag) {
            tag = escape(tag);
            tag = tag.replace("+", "%2b").replace("+", "%2b");
            return tag;
        }

        function updateVoteLink(tagName, lastPage, voteCount, tagIndex) {

            var content = voteCount;//"&#9654;&nbsp;"+voteCount;

            if(!lastPage) {
                content = content + '<img src="http://sstatic.net/so/img/ajax-loader.gif" alt="">';

                $(".votes-"+customEscape(tagName)).html(content);
                $(".votes-"+customEscape(tagName)).addClass('vote-selected');
            } else {
                allTagVotes[tagIndex] = {rank:"", votes:voteCount, name:tagName, totalQuestions:"", lastUpdated:new Date().getHours()};
                updateTag(allTagVotes[tagIndex], tagIndex)
                unsafeWindow.updateVotes(allTagVotes);
            }

        }

        var SerialAjaxExecuter = function( onComplete, delay ) {
            this.requests = [];
            this.results  = [];
            this.delay    = delay || 1;
            this.onComplete = onComplete;
        }

        SerialAjaxExecuter.prototype.addRequest = function( method, url, data, callback, format ) {
            var self = this;
            this.requests.push( {
                "method"    : method
              , "url"       : url
              , "data"      : data
              , "format"    : format
              , "callback"  : callback
            } );
            var numRequests = this.requests.length;
            if ( numRequests > 1 ) {
                this.requests[numRequests-2].callback = function( nextRequest, completionCallback ) {
                    return function( data ) {
                        completionCallback( data );
                        setTimeout( function(){ self.execute( nextRequest ); }, self.delay );
                    }
                }( this.requests[numRequests-1], this.requests[numRequests-2].callback )
            }
        }

        SerialAjaxExecuter.prototype.execute = function( request ) {
          var self = this;
          if ( 'undefined' == typeof request ) {
              request = this.requests[0];
              var lastRequest = this.requests[this.requests.length-1];
              lastRequest.callback = function( completionCallback ) {
                  return function( data  ) {
                      completionCallback( data )
                      self.onComplete( self.results );
                  }
              }( lastRequest.callback )
          }
          request.method( request.url, request.data, function( r ) {
              return function( data ) {
                  self.results.push( data );
                  r.callback( data );
              }
          }( request ) )
        }

        function firstpageCallback(data, tagName,tagIndex) {
            var voteCount = 0;

            var pages = $(".page-numbers", data).length - 6;
            var totalPages = 0;

            if(pages > 0) {
                totalPages = $(".page-numbers:eq("+pages+")", data).html();
            } else if(pages > -2) {
                //exactly 1 answer
                totalPages = 1;
            }

            function countPageVotes(data,lastPage, tagIndex){
                var pageVoteCount = 0;
                $(".vote-count-post > strong", data).each(function(){
                    //$(this).parent("span").parent("div").parent("div").next("div")
                    var curr = $(this).parents('.stats:first');

                    var answer =$(curr).children(".status:first");

                    if(answer.html() === "") {
                        pageVoteCount += parseInt($(this).text());
                    }
                });

                voteCount += pageVoteCount;

                updateVoteLink(tagName, lastPage, voteCount, tagIndex);
            }

            countPageVotes(data, (totalPages <= 1), tagIndex);

            $(function(){
                var se = new SerialAjaxExecuter( function( results ) {
                    console.log( results );
                }, 1500 );

                for (var pageCount=1; pageCount < totalPages; pageCount++) {
                    (function delayTranche() {
                        var page = pageCount;
                        var lastPage = (page === (totalPages - 1));
                        se.addRequest( $.get, "/search?q=user%3A"+userId+"+wiki%3A0+votes%3a1+[" + queryEscape(tagName) + "]&pagesize=50&page="+(page+1)+"&tab=newest", {n:pageCount},
                            function(data){countPageVotes(data, lastPage, tagIndex)}, "html" );
                    })();
                }

                se.execute();
            });

        }

        function loadTagRankings() {

            $(function(){
                var se = new SerialAjaxExecuter( function( results ) {
                    console.log( results );
                }, 1000 );

                var postTags = $('.user-stats-table:eq(3) .post-tag');

                for (var j=0; j < postTags.length; j++) {//1; j++){//
                    var currentTag = postTags[j].toString().split("[")[1].split("]")[0];
                    var tagCount = $(".user-stats-table:eq(3) a[href*='["+currentTag+"]']").next();

                    if(tagCount.html().length > 0) {
                        tagCount = tagCount.html().substring(7);

                        if(tagCount >= threshold) {
                            var tagVotes = getTagVotes(j, currentTag);

                            //first set any cached value, then check for updates
                            if(tagVotes.lastUpdated != -1) {
                                updateTag(tagVotes, j);
                            }
                            if(currentTime !== tagVotes.lastUpdated) {
                                (function() {
                                    var tagIndex = j;

                                    var tagName = currentTag;

                                    se.addRequest( $.get, "/questions/tagged?tagnames="+tagName+"&sort=stats",
                                        {n:tagCount},
                                        function(data){tagCallback(data, tagName, tagIndex)}, "html" );
                                })();
                            }
                        }
                    }
                }

                se.execute();
            });
        }

        loadTagRankings();

        var badgeTable = $('.user-stats-table:eq(4)');

        index = 0;
        $('.user-stats-table:eq(3) .post-tag').each(function(){
            var tagName = $.trim($(this).text());

            //there will be 0, 1 or 2 badges, 1 means silver, 2 means gold
            var badgeType = $(".badge", badgeTable).map(function() {
                var type = $(this).children("span:first").attr("class");
                var text = $(this).html().split("&nbsp;")[1];

                if(text === tagName) {
                    return text;
                }
            });

            if(badgeType.length > 0) {
                $(this).addClass("badge");
                $(this).html('<span class="badge'+(3 - badgeType.length)+'">&#9679;</span>&nbsp;' + tagName);
            }

            var tagDom = $('.user-stats-table:eq(3) .tag-container-'+ index +':first');

            if(tagDom.html() === "") {
                (function() {
                    var tagIndex=index;
                    tagDom.html('<a class="tag-rank '+ getTagClass(0, 0)+' votes-'+customEscape(tagName)+'" style="text-align: right;" title="get votes for '+tagName+'">&#9660;</a>');

                    $('a', tagDom).click(function(){
                        updateVoteLink(tagName, false, "", tagIndex);

                        $.get("/search?q=user%3A"+userId+"+wiki%3A0+votes%3a1+[" + queryEscape(tagName) + "]&pagesize=50&tab=newest", 
                            function(data){firstpageCallback(data, tagName, tagIndex)}, "html");
                        return false;
                    });
                })();
            }
            index++;
        });
    }
})();
19
  • 6
    The hand-drawn circle is worth a vote by itself ;-p Sep 13, 2009 at 22:28
  • Thanks, I'm quite proud of them Sep 14, 2009 at 7:36
  • It doesn't seem to be working for the [c++] tag
    – ChrisF Mod
    Sep 14, 2009 at 20:20
  • @ChrisF yes I just spotted that too and have got a further hacky workaround. Do you know of any other characters that need encoding other than . # and +? Sep 14, 2009 at 20:27
  • I can't think of any more at the moment - at least there's none on my tag list. One thing though - on c# it shows two numbers, in my case 130 then 240, why would that be?
    – ChrisF Mod
    Sep 14, 2009 at 20:31
  • I think there's a bug with the escaped characters. I've just checked c++ and it eventually settled on ▶ 521, now I know I haven't got that many votes as I don't have the tag badge
    – ChrisF Mod
    Sep 14, 2009 at 20:33
  • re 130 and 240: it shows the results with a spinner as it can only process 50 answers at a time. Once all answers have been processed you get the final number Sep 14, 2009 at 21:07
  • re 521 total, yes I agree, I checked it for user litb too as he's the top c++ user and got a similar disparity. Not sure what the issue is. I'm currently looking at alternative escaping approaches to create valid classes Sep 14, 2009 at 21:10
  • @ChrisF the problem was that c++ was not being encoded to c%2b%2b on the search, it now reports 56 votes for you on that tag Sep 14, 2009 at 21:53
  • @Rich - cheers - that's more like the total I was expecting. Glad to hear the C# total is real, still a way to go before getting the tag badge, but at least I'm over half way there ;)
    – ChrisF Mod
    Sep 14, 2009 at 22:04
  • Slightly un-obvious requirement for the script - you must be logged in for this to work.. I don't use Firefox as my main browser, so started it up to try this shiney sounding script, but nothing happened.. It stops at the line var current_user_id = ... - seems like this could be removed, as the variable is not referenced anywhere else.. but, logged in, it works! It seems I'm 59 votes away from a silver Python badge \o/
    – dbr
    Oct 10, 2009 at 23:35
  • @dbr, this was because I was working towards some extra processing to use the tag stats to get rankings and cache the data to minimise the server hits. I've updated the script now with those features now Nov 5, 2009 at 21:24
  • is this working currently? I installed it but with no effect. (Other greasemonkey scripts work fine)
    – Bozho
    Jan 13, 2010 at 15:01
  • @Bozho yes it works on my box(TM). you should see the arrows next to tags on your profile page, and when you click an arrow it will start calculating your votes Jan 31, 2010 at 12:40
  • no arrow appears whatsoever.. I'll try to re-install it once more
    – Bozho
    Jan 31, 2010 at 21:11

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .