Source: views/exams/examsAvgChartView.js

/*global require, define, console, $ */
/*jslint nomen: true, debug: true, todo: true */

/**
 * @module examsAvgChartView
 * @extends module:chartView
 */


define([
    "views/chartView",
    "underscore",
    "utils"
],
    function (chartView, _, utils) {
        "use strict";

        /**
         * @name module:examsAvgChartView
         * @description Average exams chart
         * @requires module:chartView
         * @class Backbone.View
         * @requires module:chartView
         * @requires underscore
         * @requires module:utils
         * @see module:utils
         * @constructor
         * @returns {Function} Backbone.View constructor
         */
        return chartView.extend({
            /**
             * @name module:examsAvgChartView#examType
             * @description Exam type declaration
             * @type {string}
             */
            examType: "ege",
            /**
             * @name module:examsAvgChartView#type
             * @description Chart type
             * @type {string}
             */
            type: "spline",
            /**
             * @name module:examsAvgChartView#options
             * @description Chart config extension object
             * @type {object}
             */
            options: {
                yAxis: {
                    title: {
                        text: "score".toLocaleString() + ", %"
                    }
                },
                tooltip: {
                    formatter: function () {
                        return "<b>" + this.series.name + "</b><br/>" + this.x + ": " + this.y.toPrecision(3);
                    }
                },
                plotOptions: {
                    series: {
                        point: {
                            events: {
                                click: function () {
                                    this.series.chart.options
                                        .getWidget().switchMode({
                                            year: this.category
                                        });
                                    return this.category;
                                }
                            }
                        }
                    }
                },
                series: [
                    {
                        name: "weightedAvg".toLocaleString()
                    },
                    {
                        name: "weightedAvgMathRus".toLocaleString()
                    }
                ]
            },
            /**
             * @name module:examsAvgChartView#dataproc
             * @description Data processing helpers
             * @type {object}
             */
            dataproc: {
                /**
                 * @name module:examsAvgChartView#dataproc@respDivider
                 * @description Series data formatter.
                 * Divides 1st array elements by 2nd's elements with same index
                 * @param dividends {array} Dividends array
                 * @param divisors {array} Divisors array
                 * @param range {array} Intersection indexes
                 * @param [shift] {number}
                 * @function
                 */
                respDivider: function (dividends, divisors, range, shift) {
                    shift = shift || 0;
                    var i;
                    return _.map(range, function (v, k) {
                        // undefined is not a null for Highcharts.js
                        i = dividends[v - shift];
                        return typeof i === 'number' && !isNaN(i) ?
                                i / (divisors[v - shift] || 1) :
                                null;
                    });
                }
            },
            /**
             * @name module:examsAvgChartView#update
             * @description Updates chart state by new data
             * @function
             * @param [data] {array} new data
             * @returns {object|undefined} chart.series or undefined
             */
            update: function (data) {
                // If we got the same data as rendered skip below
                // TODO: _.isEqual costs a lot for big collections. Think about it
                if (this.chart && data && !_.isEqual(this.data, data)) {

                    this.data = data;

                    var view = this,
                        min = Infinity,
                        max = -Infinity,
                        range,
                        year,
                        exam = {},
                        weight = {},
                        series = this.chart.series,

                    // Average math & rus exam result is shown separately
                        weightMathRus = {},
                        examMathRus = {};

                    // There's no way to predict exams date range.
                    // May be this value should be defined on server-side

                    _.each(data, function (item) {
                        // Previous API support
                        year = item.year || (new Date(item.date)).getFullYear();
                        item.year = year;
                        /* istanbul skip else */
                        if (year > max) {
                            max = year;
                        }
                        /* istanbul skip else */
                        if (year < min) {
                            min = year;
                        }
                    });

                    // Weighted data extraction
                    _.each(data, function (item) {
                        year = item.year - min;
                        // exam[year] is undefined by default
                        exam[year] = 100 * item.average / item.limit * item.total + (exam[year] || 0);
                        weight[year] = item.total + (weight[year] || 0);

                        /* istanbul skip else */
                        if (item.name === "math" || item.name === "russian") {
                            examMathRus[year] = 100 * item.average / item.limit * item.total + (examMathRus[year] || 0);
                            weightMathRus[year] = item.total + (weightMathRus[year] || 0);
                        }
                    });

                    // x axis range
                    range = _.range(min, max + 1);
                    // false second argument is needed to prevent useless redraw
                    series[0].update(
                        {data: this.dataproc.respDivider(exam, weight, range, min)},
                        false
                    );
                    series[1].update(
                        {data: this.dataproc.respDivider(examMathRus, weightMathRus, range, min)},
                        false
                    );
                    this.chart.xAxis[0].setCategories(range, false);

                    this.redraw();
                    return series;
                }
                return;
            }
        });
    });