(function($) {
    // "use strict";
    var baseConfig = {
       chart: {
           backgroundColor: null
       },
       title: {
          text: null
       },
       plotArea: {
          shadow: null,
          borderWidth: null
       },
       tooltip: {
          enabled: false
       },
       plotOptions: {
          pie: {
             allowPointSelect: false,
             dataLabels: {
                enabled: false,
                color: '#333'
             },
             animation: false,
             size:190
          }
       },
       credits: {enabled:false},
       legend: {
          enabled:false
       },
       series: [{
            type: 'pie'
       }]
    };
    
    $.fn.topChart = function(cfg) {
        $(this).each(function() {
            var $self   = $(this),
                $win    = $(window),
                $chart  = $self.find('.piechart'),
                hChart,
                $table  = $self.find('table'),
                metric  = $table.attr('data-metric'),
                view    = {
                    'metric': metric,
                    'group' : 'all'
                };

            // reload the data when the view's range is modified.
            $win.on('changeview', function(e, newView) {
                // we only want to respond to changes in range.
                if (!newView.range) return;
                $self.addClass('loading');
                $self.removeClass('nodata');
                _.extend(view, {'range' : normalizeRange(newView.range)});
                $.when(z.StatsManager.getDataRange(view))
                 .then(function(data) {
                    generateRankedList(data, render);
                 });
            });

            // We take the data (aggregated to one row)
            function generateRankedList(data, done) {
                if (data.empty) {
                    $self.removeClass('loading');
                    $self.addClass('nodata');
                    if (hChart && hChart.destroy) hChart.destroy();
                    $table.html('');
                    return;
                }
                var totalValue = 0,
                    otherValue = 0;
                data = data[data.firstIndex].data;
                if (_.isEmpty(data)) return;
                // Sum all fields.
                _.each(data, function(val) {
                    totalValue += val;
                });
                // Convert all fields to percentages and prettify names.
                var rankedList = _.map(data, function(val, key) {
                    var field = key.split("|").slice(-1)[0];
                    return [z.StatsManager.getPrettyName(metric, field),
                            val, val/totalValue*100];
                });
                // Sort by value.
                rankedList = _.sortBy(rankedList, function(a) {
                    return -a[1];
                });
                // Calculate the 'Other' percentage
                for (var i=5; i<rankedList.length; i++) {
                    otherValue += rankedList[i][1];
                }
                // Take the top 5 values and append an 'Other' row.
                rankedList = rankedList.slice(0,5);
                rankedList.push([gettext('Other'), otherValue, otherValue/totalValue*100]);
                // Move on with our lives.
                done(rankedList);
            }

            var tableRow = template("<tr><td>{0}</td><td>{1}</td><td>({2}%)</td></tr>");

            function render(data) {
                var newBody = "<tbody>";
                _.each(data, function(row) {
                    var pct = Math.round(row[2]);
                        num = Highcharts.numberFormat(row[1], 0);
                    if (pct < 1) pct = "<1";
                    newBody += tableRow([row[0], num, pct]);
                });
                newBody += "</tbody>";
                $table.html(newBody);

                // set up chart.
                var newConfig = _.clone(baseConfig),
                    row;
                newConfig.chart.renderTo = $chart[0];
                newConfig.series[0].data = _.map(data, function(r) { return r.slice(0,2); });
                hChart = new Highcharts.Chart(newConfig);
                for (i = 0; i < data.length; i++) {
                   row = $table.find('tr').eq(i);
                   row.children().eq(0).append($("<b class='seriesdot' style='background:" + hChart.series[0].data[i].color + "'>&nbsp;</b>"));
                }
                $self.removeClass('loading');
            }
        });
    };
})(jQuery);
@@ -692,20 +692,16 @@
 %0A    %7D;%0A
-    
 %0A    $.f
@@ -2643,16 +2643,41 @@
 return %5B
+%0A                        
 z.StatsM
@@ -2741,35 +2741,82 @@
     
-    val, val/totalValue*100
+val,%0A                        (val / totalValue) * 100%0A                    
 %5D;%0A 
@@ -3323,38 +3323,100 @@
 sh(%5B
-gettext('Other'), otherValue, 
+%0A                    gettext('Other'),%0A                    otherValue,%0A                    (
 othe
@@ -3421,17 +3421,19 @@
 herValue
-/
+ / 
 totalVal
@@ -3438,12 +3438,32 @@
 alue
-*100
+) * 100%0A                
 %5D);%0A
@@ -3610,16 +3610,30 @@
 %3C/td%3E%3Ctd
+ title=%5C%22%7B3%7D%5C%22
 %3E%7B1%7D%3C/td
@@ -3808,32 +3808,66 @@
 var 
-pct = Math.round(
+title = row%5B0%5D;%0A                    var raw_value = 
 row%5B
-2%5D)
+1%5D
 ;%0A  
@@ -3888,15 +3888,111 @@
     
-    num
+var value = Highcharts.numberFormat(raw_value, raw_value %3C 10 ? 1 : 0);%0A                    var percent
  = H
@@ -4022,16 +4022,17 @@
 row%5B
-1
+2
 %5D, 0);%0A
+%0A
     
@@ -4056,27 +4056,84 @@
 f (p
-c
+ercen
 t %3C 1) 
-pct = %22%3C1%22;
+%7B%0A                        percent = %22%3C1%22;%0A                    %7D%0A
 %0A   
@@ -4174,24 +4174,40 @@
 ow(%5B
-row%5B0%5D, num, pct
+title, value, percent, raw_value
 %5D);%0A
@@ -4975,8 +4975,9 @@
 jQuery);
+%0A

Enhance stats top charts (#14641)