Completed
Push — master ( caeaba...1483c0 )
by MusikAnimal
24s
created

window.setupMonthYearChart   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 2
c 1
b 0
f 1
nc 2
dl 0
loc 12
rs 9.4285
nop 1
1
$(function () {
2
    // Don't do anything if this isn't a Edit Counter page.
3
    if ($("body.ec").length === 0) {
4
        return;
5
    }
6
7
    // Set up charts.
8
    $(".chart-wrapper").each(function () {
9
        var chartType = $(this).data("chart-type");
10
        if ( chartType === undefined ) {
11
            return false;
12
        }
13
        var data = $(this).data("chart-data");
14
        var labels = $(this).data("chart-labels");
15
        var $ctx = $("canvas", $(this));
16
17
        /** global: Chart */
18
        new Chart($ctx, {
0 ignored issues
show
Unused Code Best Practice introduced by
The object created with new Chart($ctx, {Identif...e))))),false,false)))}) is not used but discarded. Consider invoking another function instead of a constructor if you are doing this purely for side effects.
Loading history...
19
            type: chartType,
20
            data: {
21
                labels: labels,
22
                datasets: [ { data: data } ]
23
            }
24
        });
25
26
        return undefined;
27
    });
28
29
    // Load recent global edits' HTML via AJAX, to not slow down the initial page load.
30
    // Only load if container is present, which is missing in subroutes, e.g. ec-namespacetotals, etc.
31
    var $latestGlobalContainer = $("#latestglobal-container");
32
    if ($latestGlobalContainer[0]) {
33
        /** global: xtBaseUrl */
34
        var url = xtBaseUrl + 'ec-latestglobal/'
35
            + $latestGlobalContainer.data("project") + '/'
36
            + $latestGlobalContainer.data("username") + '?htmlonly=yes';
37
        $.ajax({
38
            url: url,
39
            timeout: 30000
40
        }).done(function (data) {
41
            $latestGlobalContainer.replaceWith(data);
42
        }).fail(function (_xhr, _status, message) {
43
            $latestGlobalContainer.replaceWith(
44
                $.i18n('api-error', 'Global contributions API: <code>' + message + '</code>')
45
            );
46
        });
47
    }
48
49
    // Set up namespace toggle chart.
50
    setupToggleTable(window.namespaceTotals, window.namespaceChart, null, function (newData) {
51
        var total = 0;
52
        Object.keys(newData).forEach(function (namespace) {
53
            total += parseInt(newData[namespace], 10);
54
        });
55
        var namespaceCount = Object.keys(newData).length;
56
        $('.namespaces--namespaces').text(
57
            namespaceCount.toLocaleString() + " " +
58
            $.i18n('num-namespaces', namespaceCount)
59
        );
60
        $('.namespaces--count').text(total.toLocaleString());
61
    });
62
});
63
64
/**
65
 * Set up the monthcounts or yearcounts chart.
66
 * @param {String} id 'year' or 'month'.
67
 * @param {Array} datasets Datasets grouped by mainspace.
68
 * @param {Array} labels The bare labels for the y-axis (years or months).
69
 * @param {Number} maxTotal Maximum value of year/month totals.
70
 */
71
window.setupMonthYearChart = function (id, datasets, labels, maxTotal) {
72
    /**
73
     * Namespaces that have been excluded from view via clickable
74
     * labels above the chart.
75
     * @type {Array}
76
     */
77
    var excludedNamespaces = [];
78
79
    /**
80
     * Number of digits of the max month/year total. We want to keep this consistent
81
     * for aesthetic reasons, even if the updated totals are fewer digits in size.
82
     * @type {Number}
83
     */
84
    var maxDigits = maxTotal.toString().length;
85
86
    /** @type {Array} Labels for each namespace. */
87
    var namespaces = datasets.map(function (dataset) {
88
        return dataset.label;
89
    });
90
91
    /**
92
     * Build the labels for the y-axis of the year/monthcount charts,
93
     * which include the year/month and the total number of edits across
94
     * all namespaces in that year/month.
95
     */
96
    function getYAxisLabels()
97
    {
98
        var labelsAndTotals = {};
99
        datasets.forEach(function (namespace) {
100
            if (excludedNamespaces.indexOf(namespace.label) !== -1) {
101
                return;
102
            }
103
104
            namespace.data.forEach(function (count, index) {
105
                if (!labelsAndTotals[labels[index]]) {
106
                    labelsAndTotals[labels[index]] = 0;
107
                }
108
                labelsAndTotals[labels[index]] += count;
109
            });
110
        });
111
112
        // Format labels with totals next to them. This is a bit hacky,
113
        // but it works! We use tabs (\t) to make the labels/totals
114
        // for each namespace line up perfectly.
115
        // The caveat is that we can't localize the numbers because
116
        // the commas are not monospaced :(
117
        return Object.keys(labelsAndTotals).map(function (year) {
118
            var digitCount = labelsAndTotals[year].toString().length;
119
            var numTabs = (maxDigits - digitCount) * 2;
120
121
            // +5 for a bit of extra spacing.
122
            return year + Array(numTabs + 5).join("\t") +
123
                labelsAndTotals[year];
124
        });
125
    }
126
127
    window[id + 'countsChart'] = new Chart($('#' + id + 'counts-canvas'), {
128
        type: 'horizontalBar',
129
        data: {
130
            labels: getYAxisLabels(),
131
            datasets: datasets
132
        },
133
        options: {
134
            tooltips: {
135
                intersect: true,
136
                callbacks: {
137
                    label: function (tooltip) {
138
                        return tooltip.xLabel.toLocaleString();
139
                    },
140
                    title: function (tooltip) {
141
                        var yLabel = tooltip[0].yLabel.replace(/\t.*/, '');
142
                        return yLabel + ' - ' + namespaces[tooltip[0].datasetIndex];
143
                    }
144
                }
145
            },
146
            responsive: true,
147
            maintainAspectRatio: false,
148
            scales: {
149
                xAxes: [{
150
                    stacked: true,
151
                    ticks: {
152
                        beginAtZero: true,
153
                        callback: function (value) {
154
                            if (Math.floor(value) === value) {
155
                                return value.toLocaleString();
156
                            }
157
                        }
158
                    }
159
                }],
160
                yAxes: [{
161
                    stacked: true
162
                }]
163
            },
164
            legend: {
165
                // Happens when the user enables/disables a namespace via the
166
                // labels above the chart.
167
                onClick: function (e, legendItem) {
168
                    // Update totals, skipping over namespaces that have been excluded.
169
                    if (legendItem.hidden) {
170
                        excludedNamespaces = excludedNamespaces.filter(function (namespace) {
171
                            return namespace !== legendItem.text;
172
                        });
173
                    } else {
174
                        excludedNamespaces.push(legendItem.text);
175
                    }
176
177
                    // Update labels with the new totals.
178
                    window[id + 'countsChart'].config.data.labels = getYAxisLabels();
179
180
                    // Yield to default onClick event, which re-renders the chart.
181
                    Chart.defaults.global.legend.onClick.call(this, e, legendItem);
182
                }
183
            }
184
        }
185
    });
186
}
187