What is meta? ×
Meta Stack Exchange is where users like you discuss bugs, features, and support issues that affect the software powering all 134 Stack Exchange communities.

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?

share|improve this question

2 Answers 2

up vote 8 down vote accepted

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.

share|improve this answer
    
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 '09 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? –  Rich Seller Sep 8 '09 at 19:03

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++;
        });
    }
})();
share|improve this answer
6  
The hand-drawn circle is worth a vote by itself ;-p –  Marc Gravell Sep 13 '09 at 22:28
    
Thanks, I'm quite proud of them –  Rich Seller Sep 14 '09 at 7:36
    
It doesn't seem to be working for the [c++] tag –  ChrisF Sep 14 '09 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 +? –  Rich Seller Sep 14 '09 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 Sep 14 '09 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 Sep 14 '09 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 –  Rich Seller Sep 14 '09 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 –  Rich Seller Sep 14 '09 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 –  Rich Seller Sep 14 '09 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 Sep 14 '09 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 '09 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 –  Rich Seller Nov 5 '09 at 21:24
    
is this working currently? I installed it but with no effect. (Other greasemonkey scripts work fine) –  Bozho Jan 13 '10 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 –  Rich Seller Jan 31 '10 at 12:40
    
no arrow appears whatsoever.. I'll try to re-install it once more –  Bozho Jan 31 '10 at 21:11

You must log in to answer this question.

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