<commit_before>$(main);

function main() {
    $.getJSON('./data.json', function(d) {
        var data = attachColors(groupData(d));
        var state = initializeState(data.providers);
        var update = updateAll.bind(undefined, $('.content.row'), data, state);
        var updateWithRoute, router;

        $(window).on('resize', update);

        router = initializeRouter(state, update);

        updateWithRoute = union(update, updateRoute.bind(undefined, state, router));

        createControls($('.allControlsContainer'), state, updateWithRoute);
        createLegend($('.legendContainer'), data, state, updateWithRoute);

        update();
    });

    function union() {
        var args = arguments;

        return function() {
            for(var i = 0; i < args.length; i++) {
                args[i]();
            }
        };
    }

    function initializeRouter(state, update) {
        var Router = Backbone.Router.extend({
            routes: {
                ':providers': 'providers',
                ':providers/:type': 'providersWithType',
                ':providers/:type/:amount': 'all'
            },
            providers: function(providers) {
                state.providers = providers.split(',');

                update();
            },
            providersWithType: function(providers, type) {
                state.type = type;

                this.providers(providers);
            },
            all: function(providers, type, amount) {
                state.amount = amount;

                this.providersWithType(providers, type);
            }
        });
        var router = new Router();

        Backbone.history.start();

        return router;
    }

    function updateRoute(state, router) {
        var providers = state.providers.toString();
        var type = state.type;
        var amount = state.amount;
        var url = '/' + providers + '/' + type + '/' + amount;

        router.navigate(url, {replace: true});

        localStorage.setItem('index', '/#' + url);
    }

    function initializeState(data) {
        return {
            type: '',
            amount: '',
            providers: Object.keys(data).map(idfy)
        };
    }

    function groupData(data) {
        var providers = {};
        var wholeDayInMs = 1000 * 60 * 60 * 24;

        data.providers.forEach(function(v) {
            var name = getProviderName(v.name);

            if(!(name in providers)) providers[name] = {
                types: {},
                host: getHost(v.host)
            };
            if(!(v.type in providers[name].types)) providers[name].types[v.type] = {
                latency: v.latency,
                uptime: v.downtime.map(function(v) {
                    if(v) return parseFloat(((1 - (v / wholeDayInMs)) * 100).toFixed(3));

                    return 100;
                })
            };
        });

        return {
            providers: providers,
            firstDate: new XDate(data.firstDate * 1000),
            lastDate: new XDate(data.lastDate * 1000)
        };
    }

    function getHost(name) {
        var hosts = {
            'ajax.aspnetcdn.com': 'www.asp.net/ajaxlibrary/cdn.ashx',
            'ajax.googleapis.com': 'developers.google.com/speed/libraries/'
        };

        if(name in hosts) return hosts[name];

        return name;
    }

    function toFixed(a) {
        return function(v) {
            return parseFloat(v.toFixed(a), 10);
        };
    }

    function attachColors(data) {
        var i = 0;

        for(var name in data.providers) {
            data.providers[name].color = getColor(i);

            i++;
        }

        return data;
    }

    function createControls($p, state, update) {
        createTypes($p, state, update);
        createAmounts($p, state, update);
    }

    function createTypes($p, state, update) {
        $controls($p, state, update, 'type', ['ping', 'http', 'https'], 'ping');
    }

    function createAmounts($p, state, update) {
        $controls($p, state, update, 'amount', [7, 14, 30, 90], 30);
    }

    function $controls($p, state, update, type, items, selected) {
        var $e = $('<div>',
            {'class': 'controlsContainer ' + (type + 's')}).appendTo($p);
        items.forEach($control.bind(undefined, $e, state, type, update));

        if(!state[type]) {
            $('.' + selected, $e).addClass('selected');
            state[type] = selected;
        }
    }

    function $control($p, state, type, update, name) {
        var $e = $('<a>', {'class': 'control ' + type + ' ' + name, href: '#'}).
                text(name).on('click', function(e) {
            e.preventDefault();

            $e.siblings().removeClass('selected').removeClass('label');
            $e.addClass('selected label');

            state[type] = name;

            update();
        }).appendTo($p);

        if(state[type] == name) $e.addClass('selected');
    }

    function updateAll($p, data, state) {
        updateCharts($p, data, state);
        updateLegend($p, data, state);
    }

    function updateCharts($p, data, state) {
        updateChart($('.uptimeContainer'), data, state, 'uptime', 100, function(v) {
            v.value += ' %, ' + calculateDowntime(v.value, 1) + ' downtime';

            return v;
        });
        updateChart($('.latencyContainer'), data, state, 'latency', 300, function(v) {
            v.value += ' ms';

            return v;
        });
    }

    function updateChart($p, data, state, category, height, tooltipCb) {
        var $canvas = $('.chart:first', $p);
        var ctx, width;

        if(!$canvas.length) $canvas = $('<canvas>', {'class': 'chart'}).appendTo($p);

        // dynamic width (parent width might change)
        width = $canvas.parent().width();

        $canvas.attr({width: width, height: height});

        ctx = $canvas[0].getContext('2d');

        chart(ctx, getData(data, state, category), tooltipCb);
    }

    function updateLegend($p, data, state) {
        updateType(data, state, 'latency', function(state, values) {
            return average(values.slice(-state.amount)).toFixed(1) + ' ms';
        });

        updateType(data, state, 'uptime', function(state, values) {
            var val = average(values.slice(-state.amount));

            return $('<abbr>').attr('title', calculateDowntime(val, 30) + ' downtime').text(val.toFixed(3) + ' %');
        });
    }

    function calculateDowntime(val, amount) {
        var epochStart = new Date(0);
        var downTime = new Date((100 - val) / 100 * 60 * 60 * 24 * 1000 * amount);
        var hours = downTime.getHours() - epochStart.getHours();
        var minutes = downTime.getMinutes();
        var seconds = downTime.getSeconds();
        var milliseconds = downTime.getMilliseconds();
        var ret = '';

        if(hours) ret += hours + 'h ';
        if(minutes) ret += minutes + 'min ';

        ret += seconds + 's';

        if(!hours && !minutes) ret += ' ' + milliseconds + 'ms';

        return ret;
    }

    function updateType(data, state, type, calculateValue) {
        var providers = data.providers;
        var tdClass, value, name;

        for(name in providers) {
            value = 'NA';
            provider = providers[name];
            tdClass = idfy(name);

            if(state.type in provider.types) {
                values = provider.types[state.type][type];

                value = values && calculateValue(state, values);
            }

            $('.' + type + '.' + tdClass).html(value);
        }
    }

    function chart(ctx, data, tooltipCb) {
        // https://github.com/nnnick/Chart.js/issues/76
        var min = minimum(data.datasets.map(op(minimum, prop('data'))));
        var max = maximum(data.datasets.map(op(maximum, prop('data'))));
        var opts;

        if(max == min) {
            opts = {
                animation: true,
                animationSteps: 10,
                datasetFill: false,
                scaleOverride : true,
                scaleSteps : 3,
                scaleStepWidth : 1,
                scaleStartValue : max - 3
            };
        }
        else {
            opts = {
                animation: true,
                animationSteps: 10,
                datasetFill: false,
                pointDot: true,
                scaleShowGridLines: true,
                scaleGridLineColor: 'rgba(224,224,224,0.5)',
                scaleGridLineWidth: 1,
                pointDotRadius: 3,
                bezierCurve: false
            };
        }

        new Chart(ctx, {}, tooltipCb).Line(data, opts);
    }

    function op(fn, accessor) {
        return function(v) {
            return fn(accessor(v));
        };
    }

    function minimum(a) {
        return a.slice().sort()[0];
    }

    function maximum(a) {
        return a.slice().sort()[a.length - 1];
    }

    function createLegend($container, data, state, update) {
        var $table = $('<table>', {'class': 'legend'}).appendTo($container);
        var $header = $('<tr>').appendTo($table);
        var providers = data.providers;
        var provider, color, name, host;

        $('<th>', {'class': 'names'}).html(' ').appendTo($header);

        for(name in providers) {
            provider = providers[name];
            host = provider.host;
            color = provider.color;

            createTh(state, name, host, color, update).appendTo($header);
        }

        createRow(state, providers, 'latency', 'Latency in ms, the lower the better', 'ms').appendTo($table);
        createRow(state, providers, 'uptime', 'Uptime in percentage. The higher the better. Note that downtimes are given on hover as an estimate per 30 days.', '%').appendTo($table);
    }

    function createTh(state, name, host, color, update) {
        var thClass = idfy(name);
        var $e = $('<th>', {'class': 'cdn ' + thClass}).css({
            'background-color': colorToHex(color)
        });
        var inverseColor = colorToHex(flipColor(color));
        $('<a>', {href: 'http://' + host}).css({
            'color': inverseColor
        }).text(name).appendTo($e);

        var $icon = $('<i>', {'class': 'visibility foundicon-eyeball'}).css({
            'color': inverseColor
        }).on('click', function() {
            $icon.toggleClass('selected');

            toggleItem(state.providers, idfy(name), $icon.hasClass('selected'));

            update();
        }).appendTo($e);

        if(within(state.providers, thClass)) $icon.addClass('selected');

        return $e;
    }

    function toggleItem(arr, k, v) {
        var i;

        if(v) {
            if(!within(arr, k)) arr.push(k);
        }
        else {
            i = arr.indexOf(k);

            if(i >= 0) arr.splice(i, 1);
        }
    }

    function idfy(val) {
        return val.toLowerCase().replace(/[ \-\(\)]+/g, '_').replace(/\.+/g, '');
    }

    function createRow(state, providers, type, tooltip, unit) {
        var $row = $('<tr>');
        var name, provider, values, value, tdClass;

        var $td = $('<td>', {'class': type}).appendTo($row);
        $('<abbr>', {title: tooltip}).text(type).appendTo($td);

        for(name in providers) {
            provider = providers[name];
            tdClass = idfy(name);

            $('<td>', {'class': type + ' ' + tdClass}).appendTo($row);
        }

        return $row;
    }

    function average(arr) {
        if(!arr) return;

        return sum(arr) / arr.length;
    }

    function sum(arr) {
        if(!arr) return;

        return arr.reduce(function(a, b) {
            return a + b;
        });
    }

    function getData(data, state, category) {
        return {
            labels: getLabels(data.lastDate, state.amount),
            datasets: getDatasets(data.providers, state, category)
        };
    }

    function getLabels(lastDate, amount) {
        var ret = [];
        var len = amount;
        var i, d;

        // TODO: move these rules elsewhere
        if(amount == 90) len = 30;

        for(i = 0; i < len; i++) {
            d = lastDate.clone();

            // TODO: move these rules elsewhere
            if(amount == 90) d.addDays(-(90 - i * 3 - 1));
            else d.addDays(-(amount - i - 1));

            ret.push(d.toString('dd MMM'));
        }

        return ret;
    }

    function getDatasets(providers, state, category) {
        var ret = [];
        var provider, color, data, offset;

        for(var name in providers) {
            provider = providers[name];
            color = colorToHex(provider.color);

            if(!within(state.providers, idfy(name))) continue;
            if(!(state.type in provider.types)) continue;
            if(!(category in provider.types[state.type])) continue;

            data = provider.types[state.type][category];

            offset = state.amount - data.length;
            if(offset > 0) data = zeroes(offset).concat(data);
            else data = data.slice(-state.amount);

            ret.push({
                strokeColor: color,
                pointColor: color,
                pointStrokeColor: color,
                data: pickPoints(state.amount, data)
            });
        }

        return ret;
    }

    function pickPoints(amount, data) {
        // TODO: maybe there's a better way? now it skips the contribution of
        // the neighboring days
        if(amount == 90) return everyNth(3, data);

        return data;
    }

    function everyNth(n, data) {
        return data.filter(function(v, i) {
            return !(i % n);
        });
    }

    function zeroes(amount) {
        var ret = [];
        var i;

        for(i = 0; i < amount; i++) ret.push(0);

        return ret;
    }

    function within(arr, v) {
        return arr.indexOf(v) >= 0;
    }

    function getProviderName(fullname) {
        return fullname.split(' ')[0];
    }

    function flipColor(color) {
        /* flips color to black or white based on brightness */
        /* http://javascriptrules.com/2009/08/05/css-color-brightness-contrast-using-javascript/ */
        var brightness = (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000;

        if(brightness > 125) return [0, 0, 0];

        return [255, 255, 255];
    }

    function colorToHex(color) {
        return '#' + color.map(function(v) {
            return v.toString(16);
        }).join('');
    }

    function getColor(index) {
        return [
            [216, 79, 68],
            [174, 227, 214],
            [243, 213, 162],
            [93, 150, 215],
            [68, 68, 68],
            [227, 136, 235],
            [170, 221, 94]
        ][index];
    }

    function unique(arr) {
        var ret = {};

        arr.forEach(function(v) {
            ret[v] = true;
        });

        return Object.keys(ret);
    }

    function prop(name) {
        return function(v) {
            return v[name];
        };
    }
}
<commit_msg>Display downtime on legend<commit_after>$(main);

function main() {
    $.getJSON('./data.json', function(d) {
        var data = attachColors(groupData(d));
        var state = initializeState(data.providers);
        var update = updateAll.bind(undefined, $('.content.row'), data, state);
        var updateWithRoute, router;

        $(window).on('resize', update);

        router = initializeRouter(state, update);

        updateWithRoute = union(update, updateRoute.bind(undefined, state, router));

        createControls($('.allControlsContainer'), state, updateWithRoute);
        createLegend($('.legendContainer'), data, state, updateWithRoute);

        update();
    });

    function union() {
        var args = arguments;

        return function() {
            for(var i = 0; i < args.length; i++) {
                args[i]();
            }
        };
    }

    function initializeRouter(state, update) {
        var Router = Backbone.Router.extend({
            routes: {
                ':providers': 'providers',
                ':providers/:type': 'providersWithType',
                ':providers/:type/:amount': 'all'
            },
            providers: function(providers) {
                state.providers = providers.split(',');

                update();
            },
            providersWithType: function(providers, type) {
                state.type = type;

                this.providers(providers);
            },
            all: function(providers, type, amount) {
                state.amount = amount;

                this.providersWithType(providers, type);
            }
        });
        var router = new Router();

        Backbone.history.start();

        return router;
    }

    function updateRoute(state, router) {
        var providers = state.providers.toString();
        var type = state.type;
        var amount = state.amount;
        var url = '/' + providers + '/' + type + '/' + amount;

        router.navigate(url, {replace: true});

        localStorage.setItem('index', '/#' + url);
    }

    function initializeState(data) {
        return {
            type: '',
            amount: '',
            providers: Object.keys(data).map(idfy)
        };
    }

    function groupData(data) {
        var providers = {};
        var wholeDayInMs = 1000 * 60 * 60 * 24;

        data.providers.forEach(function(v) {
            var name = getProviderName(v.name);

            if(!(name in providers)) providers[name] = {
                types: {},
                host: getHost(v.host)
            };
            if(!(v.type in providers[name].types)) providers[name].types[v.type] = {
                latency: v.latency,
                uptime: v.downtime.map(function(v) {
                    if(v) return parseFloat(((1 - (v / wholeDayInMs)) * 100).toFixed(3));

                    return 100;
                }),
                downtime: v.downtime
            };
        });

        return {
            providers: providers,
            firstDate: new XDate(data.firstDate * 1000),
            lastDate: new XDate(data.lastDate * 1000)
        };
    }

    function getHost(name) {
        var hosts = {
            'ajax.aspnetcdn.com': 'www.asp.net/ajaxlibrary/cdn.ashx',
            'ajax.googleapis.com': 'developers.google.com/speed/libraries/'
        };

        if(name in hosts) return hosts[name];

        return name;
    }

    function toFixed(a) {
        return function(v) {
            return parseFloat(v.toFixed(a), 10);
        };
    }

    function attachColors(data) {
        var i = 0;

        for(var name in data.providers) {
            data.providers[name].color = getColor(i);

            i++;
        }

        return data;
    }

    function createControls($p, state, update) {
        createTypes($p, state, update);
        createAmounts($p, state, update);
    }

    function createTypes($p, state, update) {
        $controls($p, state, update, 'type', ['ping', 'http', 'https'], 'ping');
    }

    function createAmounts($p, state, update) {
        $controls($p, state, update, 'amount', [7, 14, 30, 90], 30);
    }

    function $controls($p, state, update, type, items, selected) {
        var $e = $('<div>',
            {'class': 'controlsContainer ' + (type + 's')}).appendTo($p);
        items.forEach($control.bind(undefined, $e, state, type, update));

        if(!state[type]) {
            $('.' + selected, $e).addClass('selected');
            state[type] = selected;
        }
    }

    function $control($p, state, type, update, name) {
        var $e = $('<a>', {'class': 'control ' + type + ' ' + name, href: '#'}).
                text(name).on('click', function(e) {
            e.preventDefault();

            $e.siblings().removeClass('selected').removeClass('label');
            $e.addClass('selected label');

            state[type] = name;

            update();
        }).appendTo($p);

        if(state[type] == name) $e.addClass('selected');
    }

    function updateAll($p, data, state) {
        updateCharts($p, data, state);
        updateLegend($p, data, state);
    }

    function updateCharts($p, data, state) {
        updateChart($('.uptimeContainer'), data, state, 'uptime', 100, function(v) {
            var day = 100 * 60 * 60 * 24 * 1000;
            var val = (100 - v.value) / day;

            v.value += ' %, ' + calculateDowntime(val) + ' downtime';

            return v;
        });
        updateChart($('.latencyContainer'), data, state, 'latency', 300, function(v) {
            v.value += ' ms';

            return v;
        });
    }

    function updateChart($p, data, state, category, height, tooltipCb) {
        var $canvas = $('.chart:first', $p);
        var ctx, width;

        if(!$canvas.length) $canvas = $('<canvas>', {'class': 'chart'}).appendTo($p);

        // dynamic width (parent width might change)
        width = $canvas.parent().width();

        $canvas.attr({width: width, height: height});

        ctx = $canvas[0].getContext('2d');

        chart(ctx, getData(data, state, category), tooltipCb);
    }

    function updateLegend($p, data, state) {
        updateType(data, state, 'latency', function(state, values) {
            return average(values.slice(-state.amount)).toFixed(1) + ' ms';
        });

        updateType(data, state, 'uptime', function(state, values) {
            var val = average(values.slice(-state.amount));
            var month = 100 * 60 * 60 * 24 * 1000 * 30;

            return $('<abbr>').attr('title', calculateDowntime((100 - val) / month) + ' downtime').text(val.toFixed(3) + ' %');
        });

        updateType(data, state, 'downtime', function(state, values) {
            return calculateDowntime(sum(values.slice(-state.amount)), true);
        });
    }

    function calculateDowntime(val) {
        var epochStart = new Date(0);
        var downTime = new Date(val);
        var hours = downTime.getHours() - epochStart.getHours();
        var minutes = downTime.getMinutes();
        var seconds = downTime.getSeconds();
        var milliseconds = downTime.getMilliseconds();
        var ret = '';

        if(hours) ret += hours + 'h ';
        if(minutes) ret += minutes + 'min ';

        ret += seconds + 's';

        if(!hours && !minutes) ret += ' ' + milliseconds + 'ms';

        return ret;
    }

    function updateType(data, state, type, calculateValue) {
        var providers = data.providers;
        var tdClass, value, name;

        for(name in providers) {
            value = 'NA';
            provider = providers[name];
            tdClass = idfy(name);

            if(state.type in provider.types) {
                values = provider.types[state.type][type];

                value = values && calculateValue(state, values);
            }

            $('.' + type + '.' + tdClass).html(value);
        }
    }

    function chart(ctx, data, tooltipCb) {
        // https://github.com/nnnick/Chart.js/issues/76
        var min = minimum(data.datasets.map(op(minimum, prop('data'))));
        var max = maximum(data.datasets.map(op(maximum, prop('data'))));
        var opts;

        if(max == min) {
            opts = {
                animation: true,
                animationSteps: 10,
                datasetFill: false,
                scaleOverride : true,
                scaleSteps : 3,
                scaleStepWidth : 1,
                scaleStartValue : max - 3
            };
        }
        else {
            opts = {
                animation: true,
                animationSteps: 10,
                datasetFill: false,
                pointDot: true,
                scaleShowGridLines: true,
                scaleGridLineColor: 'rgba(224,224,224,0.5)',
                scaleGridLineWidth: 1,
                pointDotRadius: 3,
                bezierCurve: false
            };
        }

        new Chart(ctx, {}, tooltipCb).Line(data, opts);
    }

    function op(fn, accessor) {
        return function(v) {
            return fn(accessor(v));
        };
    }

    function minimum(a) {
        return a.slice().sort()[0];
    }

    function maximum(a) {
        return a.slice().sort()[a.length - 1];
    }

    function createLegend($container, data, state, update) {
        var $table = $('<table>', {'class': 'legend'}).appendTo($container);
        var $header = $('<tr>').appendTo($table);
        var providers = data.providers;
        var provider, color, name, host;

        $('<th>', {'class': 'names'}).html(' ').appendTo($header);

        for(name in providers) {
            provider = providers[name];
            host = provider.host;
            color = provider.color;

            createTh(state, name, host, color, update).appendTo($header);
        }

        createRow(state, providers, 'latency', 'Latency in ms as average per day, the lower the better').appendTo($table);
        createRow(state, providers, 'uptime', 'Uptime in percentage as average per day. The higher the better. Note that downtimes are given on hover as an estimate per 30 days.').appendTo($table);
        createRow(state, providers, 'downtime', 'Total downtime').appendTo($table);
    }

    function createTh(state, name, host, color, update) {
        var thClass = idfy(name);
        var $e = $('<th>', {'class': 'cdn ' + thClass}).css({
            'background-color': colorToHex(color)
        });
        var inverseColor = colorToHex(flipColor(color));
        $('<a>', {href: 'http://' + host}).css({
            'color': inverseColor
        }).text(name).appendTo($e);

        var $icon = $('<i>', {'class': 'visibility foundicon-eyeball'}).css({
            'color': inverseColor
        }).on('click', function() {
            $icon.toggleClass('selected');

            toggleItem(state.providers, idfy(name), $icon.hasClass('selected'));

            update();
        }).appendTo($e);

        if(within(state.providers, thClass)) $icon.addClass('selected');

        return $e;
    }

    function toggleItem(arr, k, v) {
        var i;

        if(v) {
            if(!within(arr, k)) arr.push(k);
        }
        else {
            i = arr.indexOf(k);

            if(i >= 0) arr.splice(i, 1);
        }
    }

    function idfy(val) {
        return val.toLowerCase().replace(/[ \-\(\)]+/g, '_').replace(/\.+/g, '');
    }

    function createRow(state, providers, type, tooltip) {
        var $row = $('<tr>');
        var name, provider, values, value, tdClass;

        var $td = $('<td>', {'class': type}).appendTo($row);
        $('<abbr>', {title: tooltip}).text(type).appendTo($td);

        for(name in providers) {
            provider = providers[name];
            tdClass = idfy(name);

            $('<td>', {'class': type + ' ' + tdClass}).appendTo($row);
        }

        return $row;
    }

    function average(arr) {
        if(!arr) return;

        return sum(arr) / arr.length;
    }

    function sum(arr) {
        if(!arr) return;

        return arr.reduce(function(a, b) {
            return a + b;
        });
    }

    function getData(data, state, category) {
        return {
            labels: getLabels(data.lastDate, state.amount),
            datasets: getDatasets(data.providers, state, category)
        };
    }

    function getLabels(lastDate, amount) {
        var ret = [];
        var len = amount;
        var i, d;

        // TODO: move these rules elsewhere
        if(amount == 90) len = 30;

        for(i = 0; i < len; i++) {
            d = lastDate.clone();

            // TODO: move these rules elsewhere
            if(amount == 90) d.addDays(-(90 - i * 3 - 1));
            else d.addDays(-(amount - i - 1));

            ret.push(d.toString('dd MMM'));
        }

        return ret;
    }

    function getDatasets(providers, state, category) {
        var ret = [];
        var provider, color, data, offset;

        for(var name in providers) {
            provider = providers[name];
            color = colorToHex(provider.color);

            if(!within(state.providers, idfy(name))) continue;
            if(!(state.type in provider.types)) continue;
            if(!(category in provider.types[state.type])) continue;

            data = provider.types[state.type][category];

            offset = state.amount - data.length;
            if(offset > 0) data = zeroes(offset).concat(data);
            else data = data.slice(-state.amount);

            ret.push({
                strokeColor: color,
                pointColor: color,
                pointStrokeColor: color,
                data: pickPoints(state.amount, data)
            });
        }

        return ret;
    }

    function pickPoints(amount, data) {
        // TODO: maybe there's a better way? now it skips the contribution of
        // the neighboring days
        if(amount == 90) return everyNth(3, data);

        return data;
    }

    function everyNth(n, data) {
        return data.filter(function(v, i) {
            return !(i % n);
        });
    }

    function zeroes(amount) {
        var ret = [];
        var i;

        for(i = 0; i < amount; i++) ret.push(0);

        return ret;
    }

    function within(arr, v) {
        return arr.indexOf(v) >= 0;
    }

    function getProviderName(fullname) {
        return fullname.split(' ')[0];
    }

    function flipColor(color) {
        /* flips color to black or white based on brightness */
        /* http://javascriptrules.com/2009/08/05/css-color-brightness-contrast-using-javascript/ */
        var brightness = (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000;

        if(brightness > 125) return [0, 0, 0];

        return [255, 255, 255];
    }

    function colorToHex(color) {
        return '#' + color.map(function(v) {
            return v.toString(16);
        }).join('');
    }

    function getColor(index) {
        return [
            [216, 79, 68],
            [174, 227, 214],
            [243, 213, 162],
            [93, 150, 215],
            [68, 68, 68],
            [227, 136, 235],
            [170, 221, 94]
        ][index];
    }

    function unique(arr) {
        var ret = {};

        arr.forEach(function(v) {
            ret[v] = true;
        });

        return Object.keys(ret);
    }

    function prop(name) {
        return function(v) {
            return v[name];
        };
    }
}
<|endoftext|>


<commit_before>/**
 * @author Serhii Maksymchuk from Ubrainians for imCode
 * 27.03.18
 */
const originImageHeightBlock = require('imcms-origin-image-height-block');
const originImageWidthBlock = require('imcms-origin-image-width-block');
const editableImage = require('imcms-editable-image');
const previewImage = require('imcms-preview-image-area');

let saveProportions = true; // by default
const original = {};
const preview = {};
const currentSize = {};
let proportionsCoefficient;

let maxWidth, maxHeight, minWidth, minHeight;

function trimToMaxMinWidth(newWidth) {
    if (maxWidth) newWidth = Math.min(newWidth, maxWidth);
    if (minWidth) newWidth = Math.max(newWidth, minWidth);

    return newWidth;
}

function trimToMaxMinHeight(newHeight) {
    if (maxHeight) newHeight = Math.min(newHeight, maxHeight);
    if (minHeight) newHeight = Math.max(newHeight, minHeight);

    return newHeight;
}

function setWidth(newWidth, isOriginal) {
    if (isOriginal || isOriginal === undefined) {
        const $image = editableImage.getImage();
        const oldWidth = $image.width();
        const k = newWidth / oldWidth;

        const newImageLeft = k * editableImage.getBackgroundPositionX();
        const newImageBackgroundWidth = k * editableImage.getBackgroundWidth();

        $image.width(newWidth);
        editableImage.setBackgroundWidth(newImageBackgroundWidth);
        editableImage.setBackgroundPositionX(newImageLeft);

        $widthControl.val(newWidth);
    } else {
        const $image = previewImage.getPreviewImage();
        const oldWidth = $image.width();
        const k = newWidth / oldWidth;

        const newImageLeft = k * previewImage.getBackgroundPositionX();
        const newImageBackgroundWidth = k * previewImage.getBackgroundWidth();

        $image.width(newWidth);
        previewImage.setBackgroundWidth(newImageBackgroundWidth);
        previewImage.setBackgroundPositionX(newImageLeft);

        $widthControl.val(newWidth);
    }

}

function setHeight(newHeight, isOriginal) {
    if (isOriginal || isOriginal === undefined) {
        const $image = editableImage.getImage();
        const oldHeight = $image.height();
        const k = newHeight / oldHeight;

        const newImageTop = k * editableImage.getBackgroundPositionY();
        const newImageBackgroundHeight = k * editableImage.getBackgroundHeight();

        $image.height(newHeight);
        editableImage.setBackgroundHeight(newImageBackgroundHeight);
        editableImage.setBackgroundPositionY(newImageTop);

        $heightControl.val(newHeight);
    } else {
        const $image = previewImage.getPreviewImage();
        const oldHeight = $image.height();
        const k = newHeight / oldHeight;

        const newImageTop = k * previewImage.getBackgroundPositionY();
        const newImageBackgroundHeight = k * previewImage.getBackgroundHeight();

        $image.height(newHeight);
        previewImage.setBackgroundHeight(newImageBackgroundHeight);
        previewImage.setBackgroundPositionY(newImageTop);

        $heightControl.val(newHeight);
    }
}

function setHeightProportionally(newHeight, isOriginal) {
    newHeight = trimToMaxMinHeight(newHeight);
    setHeight(newHeight, isOriginal);
    updateWidthProportionally(newHeight, isOriginal);
}

function setWidthProportionally(newWidth, isOriginal) {
    newWidth = trimToMaxMinWidth(newWidth);
    setWidth(newWidth, isOriginal);
    updateHeightProportionally(newWidth, isOriginal);
}

function updateWidthProportionally(newHeight, isOriginal) {
    const proportionalWidth = ~~(newHeight * proportionsCoefficient);
    const fixedWidth = trimToMaxMinWidth(proportionalWidth);

    (fixedWidth === proportionalWidth)
        ? setWidth(proportionalWidth, isOriginal)
        : setWidthProportionally(proportionalWidth, isOriginal); // MAY (or not) APPEAR RECURSIVE!!!11 be careful
}

function updateHeightProportionally(newWidth, isOriginal) {
    const proportionalHeight = ~~(newWidth / proportionsCoefficient);
    const fixedHeight = trimToMaxMinHeight(proportionalHeight);

    (fixedHeight === proportionalHeight)
        ? setHeight(proportionalHeight, isOriginal)
        : setHeightProportionally(proportionalHeight, isOriginal); // MAY (or not) APPEAR RECURSIVE!!!11 be careful
}

let $heightControl, $widthControl;

module.exports = {
    resetToOriginal(imageData) {
        this.setHeightStrict(0, original.height, true);
        this.setWidthStrict(0, original.width, true);

        let width, height;

        if (minWidth && minHeight) {
            width = minWidth;
            height = minHeight;

        } else {
            width = original.width;
            height = original.height;
        }

        this.setCurrentSize(width, height);
        this.updateSizing(imageData, true);
    },
    setCurrentSize(width, height) {
        currentSize.width = width;
        currentSize.height = height;
        proportionsCoefficient = currentSize.width / currentSize.height;
    },
    getOriginal: () => original,
    setOriginal(originalWidth, originalHeight) {
        originImageHeightBlock.setValue(originalHeight);
        originImageWidthBlock.setValue(originalWidth);

        original.width = originalWidth;
        original.height = originalHeight;

        this.setCurrentSize(originalWidth, originalHeight);
    },

    getPreview: () => preview,
    setPreview(previewWidth, previewHeight) {
        preview.width = previewWidth;
        preview.height = previewHeight;

        this.setCurrentSize(previewWidth, previewHeight);
    },
    setWidthControl($control) {
        $widthControl = $control
    },

    setHeightControl($control) {
        $heightControl = $control
    },

    isProportionsLockedByStyle() {
        return minWidth && minHeight
    },

    isSaveProportionsEnabled: () => saveProportions,

    getProportionsCoefficient: () => proportionsCoefficient,

    toggleSaveProportions: () => (saveProportions = !saveProportions),

    enableSaveProportions() {
        saveProportions = true;
    },

    setHeight(newValue, isOriginal) {
        setHeight(trimToMaxMinHeight(newValue), isOriginal);
    },

    setWidth(newValue, isOriginal) {
        setWidth(trimToMaxMinWidth(newValue), isOriginal);
    },

    /**
     * Setting without any proportions or min/max checking
     * @param padding for cropped images
     * @param newWidth
     * @param isOriginal
     */
    setWidthStrict(padding, newWidth, isOriginal) {
        if (isOriginal) {
            editableImage.setBackgroundWidth(original.width);
            editableImage.getImage().width(newWidth);

            if (padding >= 0) editableImage.setBackgroundPositionX(-padding);

            $widthControl.val(newWidth);
        } else {
            previewImage.setBackgroundWidth(preview.width);
            previewImage.getPreviewImage().width(newWidth);

            if (padding >= 0) previewImage.setBackgroundPositionX(-padding);

            $widthControl.val(newWidth);
        }
    },

    /**
     * Setting without any proportions or min/max checking
     * @param padding for cropped images
     * @param newHeight
     * @param isOriginal
     */
    setHeightStrict(padding, newHeight, isOriginal) {
        if (isOriginal) {
            editableImage.setBackgroundHeight(original.height);
            editableImage.getImage().height(newHeight);

            if (padding >= 0) editableImage.setBackgroundPositionY(-padding);

            $heightControl.val(newHeight);
        } else {
            previewImage.setBackgroundHeight(preview.height);
            previewImage.getPreviewImage().height(newHeight);

            if (padding >= 0) previewImage.setBackgroundPositionY(-padding);

            $heightControl.val(newHeight);
        }
    },

    setHeightProportionally: setHeightProportionally,

    setWidthProportionally: setWidthProportionally,

    getWidth: () => editableImage.getImage().width(),

    getHeight: () => editableImage.getImage().height(),

    getPreviewWidth: () => previewImage.getPreviewImage().width(),

    getPreviewHeight: () => previewImage.getPreviewImage().height(),

    setMaxWidth(maxWidthValue) {
        maxWidth = maxWidthValue
    },

    setMaxHeight(maxHeightValue) {
        maxHeight = maxHeightValue
    },

    setMinWidth(minWidthValue) {
        minWidth = minWidthValue
    },

    setMinHeight(minHeightValue) {
        minHeight = minHeightValue
    },

    /**
     * Can be used after setting strict w/h to update all proportions and min/max restrictions
     * @param isOriginal value for original image or original tab data
     * @param imageData
     * @param ignoreCropping
     */
    updateSizing(imageData, ignoreCropping, isOriginal) {
        const originalProportionsK = isOriginal ? original.width / original.height : preview.width / preview.height;

        if (minWidth && minHeight && (originalProportionsK !== (minWidth / minHeight))) {
            const width = minWidth;
            const height = minHeight;
            const restrictedProportionsK = minWidth / minHeight;

            const dX = isOriginal ? width / original.width : width / preview.width;
            const dY = isOriginal ? height / original.height : height / preview.height;

            let newWidth, newHeight, cropHeight, cropWidth;

            const cropRegion = imageData.cropRegion || (imageData.cropRegion = {
                cropX1: 0,
                cropX2: 0,
                cropY1: 0,
                cropY2: 0,
            });

            const widthCurrentObj = isOriginal ? original.width : preview.width;
            const heightCurrentObj = isOriginal ? original.height : preview.height;

            if (dX > dY) {
                const croppedWidth = Math.max(0, cropRegion.cropX2 - cropRegion.cropX1);

                newWidth = Math.max(width, ignoreCropping ? croppedWidth : widthCurrentObj);
                newWidth = Math.min(newWidth, ignoreCropping ? croppedWidth : widthCurrentObj);
                newHeight = ~~(newWidth / proportionsCoefficient);
                cropHeight = ~~(newWidth / restrictedProportionsK);
                cropWidth = newWidth;
            } else {
                const croppedHeight = Math.max(0, cropRegion.cropY2 - cropRegion.cropY1);

                newHeight = Math.max(height, ignoreCropping ? croppedHeight : heightCurrentObj);
                newHeight = Math.min(newHeight, ignoreCropping ? croppedHeight : heightCurrentObj);
                newWidth = ~~(newHeight * proportionsCoefficient);
                cropWidth = ~~(newHeight * restrictedProportionsK);
                cropHeight = newHeight;
            }

            this.setWidthStrict(cropRegion.cropX1, newWidth, isOriginal);
            this.setHeightStrict(cropRegion.cropY1, newHeight, isOriginal);

            if (!ignoreCropping) {
                cropRegion.cropX2 = cropWidth;
                cropRegion.cropY2 = cropHeight;
            }
        }

        setHeightProportionally(currentSize.height, isOriginal);
        setWidthProportionally(currentSize.width, isOriginal);
    },

    clearData() {
        maxWidth = null;
        maxHeight = null;
        minWidth = null;
        minHeight = null;
        saveProportions = true;
        proportionsCoefficient = 1;
        original.width = null;
        original.height = null;
        preview.width = null;
        preview.height = null;
    },
};
<commit_msg>Issue IMCMS-508: Improve the image editor - Add modules set size control; - Fix W/H control<commit_after>/**
 * @author Serhii Maksymchuk from Ubrainians for imCode
 * 27.03.18
 */
const originImageHeightBlock = require('imcms-origin-image-height-block');
const originImageWidthBlock = require('imcms-origin-image-width-block');
const editableImage = require('imcms-editable-image');
const previewImage = require('imcms-preview-image-area');

let saveProportions = true; // by default
const original = {};
const preview = {};
const currentSize = {};
const currentPrevSize = {};
let proportionsCoefficient;

let maxWidth, maxHeight, minWidth, minHeight;

function trimToMaxMinWidth(newWidth) {
    if (maxWidth) newWidth = Math.min(newWidth, maxWidth);
    if (minWidth) newWidth = Math.max(newWidth, minWidth);

    return newWidth;
}

function trimToMaxMinHeight(newHeight) {
    if (maxHeight) newHeight = Math.min(newHeight, maxHeight);
    if (minHeight) newHeight = Math.max(newHeight, minHeight);

    return newHeight;
}

function setWidth(newWidth, isOriginal) {
    if (isOriginal || isOriginal === undefined) {
        const $image = editableImage.getImage();
        const oldWidth = $image.width();
        const k = newWidth / oldWidth;

        const newImageLeft = k * editableImage.getBackgroundPositionX();
        const newImageBackgroundWidth = k * editableImage.getBackgroundWidth();

        $image.width(newWidth);
        editableImage.setBackgroundWidth(newImageBackgroundWidth);
        editableImage.setBackgroundPositionX(newImageLeft);

        $widthControl.val(newWidth);
    } else {
        const $image = previewImage.getPreviewImage();
        const oldWidth = $image.width();
        const k = newWidth / oldWidth;

        const newImageLeft = k * previewImage.getBackgroundPositionX();
        const newImageBackgroundWidth = k * previewImage.getBackgroundWidth();

        $image.width(newWidth);
        previewImage.setBackgroundWidth(newImageBackgroundWidth);
        previewImage.setBackgroundPositionX(newImageLeft);

        $widthPreviewControl.val(newWidth);
    }

}

function setHeight(newHeight, isOriginal) {
    if (isOriginal || isOriginal === undefined) {
        const $image = editableImage.getImage();
        const oldHeight = $image.height();
        const k = newHeight / oldHeight;

        const newImageTop = k * editableImage.getBackgroundPositionY();
        const newImageBackgroundHeight = k * editableImage.getBackgroundHeight();

        $image.height(newHeight);
        editableImage.setBackgroundHeight(newImageBackgroundHeight);
        editableImage.setBackgroundPositionY(newImageTop);

        $heightControl.val(newHeight);
    } else {
        const $image = previewImage.getPreviewImage();
        const oldHeight = $image.height();
        const k = newHeight / oldHeight;

        const newImageTop = k * previewImage.getBackgroundPositionY();
        const newImageBackgroundHeight = k * previewImage.getBackgroundHeight();

        $image.height(newHeight);
        previewImage.setBackgroundHeight(newImageBackgroundHeight);
        previewImage.setBackgroundPositionY(newImageTop);

        $heightPreviewControl.val(newHeight);
    }
}

function setHeightProportionally(newHeight, isOriginal) {
    newHeight = trimToMaxMinHeight(newHeight);
    setHeight(newHeight, isOriginal);
    updateWidthProportionally(newHeight, isOriginal);
}

function setWidthProportionally(newWidth, isOriginal) {
    newWidth = trimToMaxMinWidth(newWidth);
    setWidth(newWidth, isOriginal);
    updateHeightProportionally(newWidth, isOriginal);
}

function updateWidthProportionally(newHeight, isOriginal) {
    const proportionalWidth = ~~(newHeight * proportionsCoefficient);
    const fixedWidth = trimToMaxMinWidth(proportionalWidth);

    (fixedWidth === proportionalWidth)
        ? setWidth(proportionalWidth, isOriginal)
        : setWidthProportionally(proportionalWidth, isOriginal); // MAY (or not) APPEAR RECURSIVE!!!11 be careful
}

function updateHeightProportionally(newWidth, isOriginal) {
    const proportionalHeight = ~~(newWidth / proportionsCoefficient);
    const fixedHeight = trimToMaxMinHeight(proportionalHeight);

    (fixedHeight === proportionalHeight)
        ? setHeight(proportionalHeight, isOriginal)
        : setHeightProportionally(proportionalHeight, isOriginal); // MAY (or not) APPEAR RECURSIVE!!!11 be careful
}

let $heightControl, $widthControl;
let $heightPreviewControl, $widthPreviewControl;

module.exports = {
    resetToOriginal(imageData) {
        this.setHeightStrict(0, original.height, true);
        this.setWidthStrict(0, original.width, true);

        let width, height;

        if (minWidth && minHeight) {
            width = minWidth;
            height = minHeight;

        } else {
            width = original.width;
            height = original.height;
        }

        this.setCurrentSize(width, height);
        this.updateSizing(imageData, true);
    },

    resetToPreview(imageData) {
        this.setHeightStrict(0, preview.height, false);
        this.setWidthStrict(0, preview.width, false);

        let width, height;

        if (minWidth && minHeight) {
            width = minWidth;
            height = minHeight;

        } else {
            width = preview.width;
            height = preview.height;
        }

        this.setCurrentPreviewSize(width, height);
        this.updateSizing(imageData, true);
    },
    setCurrentSize(width, height) {
        currentSize.width = width;
        currentSize.height = height;
        proportionsCoefficient = currentSize.width / currentSize.height;
    },
    setCurrentPreviewSize(width, height) {
        currentPrevSize.width = width;
        currentPrevSize.height = height;
        proportionsCoefficient = currentPrevSize.width / currentPrevSize.height;

    },
    getOriginal: () => original,
    setOriginal(originalWidth, originalHeight) {
        originImageHeightBlock.setValue(originalHeight);
        originImageWidthBlock.setValue(originalWidth);

        original.width = originalWidth;
        original.height = originalHeight;

        this.setCurrentSize(originalWidth, originalHeight);
    },

    getPreview: () => preview,
    setPreview(previewWidth, previewHeight) {
        preview.width = previewWidth;
        preview.height = previewHeight;

        this.setCurrentPreviewSize(previewWidth, previewHeight);
    },
    setWidthControl($control) {
        $widthControl = $control
    },

    setHeightControl($control) {
        $heightControl = $control
    },

    setPreviewWidthControl($control) {
        $widthPreviewControl = $control
    },

    setPreviewHeightControl($control) {
        $heightPreviewControl = $control
    },

    isProportionsLockedByStyle() {
        return minWidth && minHeight
    },

    isSaveProportionsEnabled: () => saveProportions,

    getProportionsCoefficient: () => proportionsCoefficient,

    toggleSaveProportions: () => (saveProportions = !saveProportions),

    enableSaveProportions() {
        saveProportions = true;
    },

    setHeight(newValue, isOriginal) {
        setHeight(trimToMaxMinHeight(newValue), isOriginal);
    },

    setWidth(newValue, isOriginal) {
        setWidth(trimToMaxMinWidth(newValue), isOriginal);
    },

    /**
     * Setting without any proportions or min/max checking
     * @param padding for cropped images
     * @param newWidth
     * @param isOriginal
     */
    setWidthStrict(padding, newWidth, isOriginal) {
        if (isOriginal) {
            editableImage.setBackgroundWidth(original.width);
            editableImage.getImage().width(newWidth);

            if (padding >= 0) editableImage.setBackgroundPositionX(-padding);

            $widthControl.val(newWidth);
        } else {
            previewImage.setBackgroundWidth(preview.width);
            previewImage.getPreviewImage().width(newWidth);

            if (padding >= 0) previewImage.setBackgroundPositionX(-padding);

            $widthPreviewControl.val(newWidth);
        }
    },

    /**
     * Setting without any proportions or min/max checking
     * @param padding for cropped images
     * @param newHeight
     * @param isOriginal
     */
    setHeightStrict(padding, newHeight, isOriginal) {
        if (isOriginal) {
            editableImage.setBackgroundHeight(original.height);
            editableImage.getImage().height(newHeight);

            if (padding >= 0) editableImage.setBackgroundPositionY(-padding);

            $heightControl.val(newHeight);
        } else {
            previewImage.setBackgroundHeight(preview.height);
            previewImage.getPreviewImage().height(newHeight);

            if (padding >= 0) previewImage.setBackgroundPositionY(-padding);

            $heightPreviewControl.val(newHeight);
        }
    },

    setHeightProportionally: setHeightProportionally,

    setWidthProportionally: setWidthProportionally,

    getWidth: () => editableImage.getImage().width(),

    getHeight: () => editableImage.getImage().height(),

    getPreviewWidth: () => previewImage.getPreviewImage().width(),

    getPreviewHeight: () => previewImage.getPreviewImage().height(),

    setMaxWidth(maxWidthValue) {
        maxWidth = maxWidthValue
    },

    setMaxHeight(maxHeightValue) {
        maxHeight = maxHeightValue
    },

    setMinWidth(minWidthValue) {
        minWidth = minWidthValue
    },

    setMinHeight(minHeightValue) {
        minHeight = minHeightValue
    },

    /**
     * Can be used after setting strict w/h to update all proportions and min/max restrictions
     * @param isOriginal value for original image or original tab data
     * @param imageData
     * @param ignoreCropping
     */
    updateSizing(imageData, ignoreCropping, isOriginal) {
        const originalProportionsK = isOriginal ? original.width / original.height : preview.width / preview.height;

        if (minWidth && minHeight && (originalProportionsK !== (minWidth / minHeight))) {
            const width = minWidth;
            const height = minHeight;
            const restrictedProportionsK = minWidth / minHeight;

            const dX = isOriginal ? width / original.width : width / preview.width;
            const dY = isOriginal ? height / original.height : height / preview.height;

            let newWidth, newHeight, cropHeight, cropWidth;

            const cropRegion = imageData.cropRegion || (imageData.cropRegion = {
                cropX1: 0,
                cropX2: 0,
                cropY1: 0,
                cropY2: 0,
            });

            const widthCurrentObj = isOriginal ? original.width : preview.width;
            const heightCurrentObj = isOriginal ? original.height : preview.height;

            if (dX > dY) {
                const croppedWidth = Math.max(0, cropRegion.cropX2 - cropRegion.cropX1);

                newWidth = Math.max(width, ignoreCropping ? croppedWidth : widthCurrentObj);
                newWidth = Math.min(newWidth, ignoreCropping ? croppedWidth : widthCurrentObj);
                newHeight = ~~(newWidth / proportionsCoefficient);
                cropHeight = ~~(newWidth / restrictedProportionsK);
                cropWidth = newWidth;
            } else {
                const croppedHeight = Math.max(0, cropRegion.cropY2 - cropRegion.cropY1);

                newHeight = Math.max(height, ignoreCropping ? croppedHeight : heightCurrentObj);
                newHeight = Math.min(newHeight, ignoreCropping ? croppedHeight : heightCurrentObj);
                newWidth = ~~(newHeight * proportionsCoefficient);
                cropWidth = ~~(newHeight * restrictedProportionsK);
                cropHeight = newHeight;
            }

            this.setWidthStrict(cropRegion.cropX1, newWidth, isOriginal);
            this.setHeightStrict(cropRegion.cropY1, newHeight, isOriginal);

            if (!ignoreCropping) {
                cropRegion.cropX2 = cropWidth;
                cropRegion.cropY2 = cropHeight;
            }
        }

        setHeightProportionally(currentSize.height, isOriginal);
        setWidthProportionally(currentSize.width, isOriginal);
    },

    clearData() {
        maxWidth = null;
        maxHeight = null;
        minWidth = null;
        minHeight = null;
        saveProportions = true;
        proportionsCoefficient = 1;
        original.width = null;
        original.height = null;
        preview.width = null;
        preview.height = null;
    },
};
<|endoftext|>


<commit_before>import Ember from 'ember';
import ol from 'openlayers';

import slMapHoverHandler from './map-hover-handler';

/**
 * This module handles the flight display page
 *
 * @constructor
 * @export
 * @param {ol.Map} map OpenLayers map object
 * @param {Ember.Component} fix_table
 * @param {Ember.Component} baro
 */
export default function slFlightDisplay(map, fix_table, baro) {
  var flight_display = {};

  /**
   * Flight collection
   * @type {slFlightCollection}
   */
  var flights = window.fixCalcService.get('flights');

  /**
   * Handler for map hover events
   * @type {slMapHoverHandler}
   */
  var map_hover_handler = slMapHoverHandler.create({
    map: map,
    flights: flights,
  });

  /**
   * Default time - the time to set when no time is set
   * @type {!Number}
   */
  var default_time = null;

  /**
   * Initialize the map, add flight path and contest layers.
   */
  flight_display.init = function() {
    map_hover_handler.set('hover_enabled', true);
    baro.set('hoverMode', true);

    setupEvents();
  };

  /**
   * Update the x-scale of the barogram
   */
  function updateBaroScale() {
    var extent = map.getView().calculateExtent(map.getSize());
    var interval = flights.getMinMaxTimeInExtent(extent);

    if (interval.max == -Infinity) {
      baro.set('timeInterval', null);
    } else {
      baro.set('timeInterval', [interval.min, interval.max]);
    }
  }

  /**
   * Setup several events...
   */
  function setupEvents() {
    // Update the baro scale when the map has been zoomed/moved.
    var update_baro_scale_on_moveend = function() {
      updateBaroScale();
      baro.draw();
    };

    map.on('moveend', update_baro_scale_on_moveend);

    // Set the time when the mouse hoves the map
    map_hover_handler.on('set_time', function(time) {
      window.fixCalcService.set('time', time || default_time);
    });

    // Update the barogram when another flight has been selected
    // in the fix table.
    fix_table.addObserver('selection', function() {
      baro.set('selection', fix_table.get('selection'));
      Ember.run.once(baro, 'draw');
    });

    // Remove a flight when the removal button has been pressed
    // in the fix table.
    fix_table.on('remove_flight', function(sfid) {
      // never remove the first flight...
      if (Ember.get(flights, 'firstObject.id') == sfid) return;

      flights.removeObjects(flights.filterBy('id', sfid));
    });

    flights.addObserver('[]', function() {
      Ember.run.once(flight_display, 'update');
    });

    window.fixCalcService.addObserver('isRunning', function() {
      var running = this.get('isRunning');

      map_hover_handler.set('hover_enabled', !running && !window.flightMap.get('cesiumEnabled'));
      baro.set('hoverMode', !running);
    });

    // Add hover and click events to the barogram.
    baro.on('barohover', function(time) {
      window.fixCalcService.set('time', time);
    });
    baro.on('baroclick', function(time) {
      window.fixCalcService.set('time', time);
    });
    baro.on('mouseout', function() {
      window.fixCalcService.set('time', default_time);
    });

    window.flightMap.addObserver('cesiumEnabled', function() {
      if (this.get('cesiumEnabled')) {
        map.un('moveend', update_baro_scale_on_moveend);

        if (!window.fixCalcService.get('isRunning')) {
          // disable mouse hovering
          map_hover_handler.set('hover_enabled', false);
        }

        map.getLayers().getArray().forEach(function(e) {
          if (e.get('name') == 'Contest') e.setVisible(false);
        });

        map.getLayers().getArray().forEach(function(e) {
          if (!e.get('base_layer') && !(e instanceof ol.layer.Vector))
            e.setVisible(false);
        });

        baro.set('timeInterval', null);
        baro.draw();

      } else {
        // Update the baro scale when the map has been zoomed/moved.
        map.on('moveend', update_baro_scale_on_moveend);

        if (!window.fixCalcService.get('isRunning')) {
          // enable mouse hovering
          map_hover_handler.set('hover_enabled', true);
        }

        map.getLayers().getArray().forEach(function(e) {
          if (e.get('name') == 'Contest') e.setVisible(true);
        });
      }
    });
  }

  /**
   * Updates the barogram
   */
  flight_display.update = function() {
    updateBaroScale();
    baro.draw();
  };

  /**
   * Returns the flights collection
   * @return {slFlightCollection}
   */
  flight_display.getFlights = function() {
    return flights;
  };

  /**
   * Set the default time. Used at the tracking page.
   * @param {!Number} time Default time to set
   */
  flight_display.setDefaultTime = function(time) {
    default_time = time;
  };

  /**
   * Returns the barogram module
   * @return {slBarogram}
   */
  flight_display.getBaro = function() {
    return baro;
  };

  flight_display.init();
  return flight_display;
}
<commit_msg>utils/flight-display: Remove unused getBaro() method<commit_after>import Ember from 'ember';
import ol from 'openlayers';

import slMapHoverHandler from './map-hover-handler';

/**
 * This module handles the flight display page
 *
 * @constructor
 * @export
 * @param {ol.Map} map OpenLayers map object
 * @param {Ember.Component} fix_table
 * @param {Ember.Component} baro
 */
export default function slFlightDisplay(map, fix_table, baro) {
  var flight_display = {};

  /**
   * Flight collection
   * @type {slFlightCollection}
   */
  var flights = window.fixCalcService.get('flights');

  /**
   * Handler for map hover events
   * @type {slMapHoverHandler}
   */
  var map_hover_handler = slMapHoverHandler.create({
    map: map,
    flights: flights,
  });

  /**
   * Default time - the time to set when no time is set
   * @type {!Number}
   */
  var default_time = null;

  /**
   * Initialize the map, add flight path and contest layers.
   */
  flight_display.init = function() {
    map_hover_handler.set('hover_enabled', true);
    baro.set('hoverMode', true);

    setupEvents();
  };

  /**
   * Update the x-scale of the barogram
   */
  function updateBaroScale() {
    var extent = map.getView().calculateExtent(map.getSize());
    var interval = flights.getMinMaxTimeInExtent(extent);

    if (interval.max == -Infinity) {
      baro.set('timeInterval', null);
    } else {
      baro.set('timeInterval', [interval.min, interval.max]);
    }
  }

  /**
   * Setup several events...
   */
  function setupEvents() {
    // Update the baro scale when the map has been zoomed/moved.
    var update_baro_scale_on_moveend = function() {
      updateBaroScale();
      baro.draw();
    };

    map.on('moveend', update_baro_scale_on_moveend);

    // Set the time when the mouse hoves the map
    map_hover_handler.on('set_time', function(time) {
      window.fixCalcService.set('time', time || default_time);
    });

    // Update the barogram when another flight has been selected
    // in the fix table.
    fix_table.addObserver('selection', function() {
      baro.set('selection', fix_table.get('selection'));
      Ember.run.once(baro, 'draw');
    });

    // Remove a flight when the removal button has been pressed
    // in the fix table.
    fix_table.on('remove_flight', function(sfid) {
      // never remove the first flight...
      if (Ember.get(flights, 'firstObject.id') == sfid) return;

      flights.removeObjects(flights.filterBy('id', sfid));
    });

    flights.addObserver('[]', function() {
      Ember.run.once(flight_display, 'update');
    });

    window.fixCalcService.addObserver('isRunning', function() {
      var running = this.get('isRunning');

      map_hover_handler.set('hover_enabled', !running && !window.flightMap.get('cesiumEnabled'));
      baro.set('hoverMode', !running);
    });

    // Add hover and click events to the barogram.
    baro.on('barohover', function(time) {
      window.fixCalcService.set('time', time);
    });
    baro.on('baroclick', function(time) {
      window.fixCalcService.set('time', time);
    });
    baro.on('mouseout', function() {
      window.fixCalcService.set('time', default_time);
    });

    window.flightMap.addObserver('cesiumEnabled', function() {
      if (this.get('cesiumEnabled')) {
        map.un('moveend', update_baro_scale_on_moveend);

        if (!window.fixCalcService.get('isRunning')) {
          // disable mouse hovering
          map_hover_handler.set('hover_enabled', false);
        }

        map.getLayers().getArray().forEach(function(e) {
          if (e.get('name') == 'Contest') e.setVisible(false);
        });

        map.getLayers().getArray().forEach(function(e) {
          if (!e.get('base_layer') && !(e instanceof ol.layer.Vector))
            e.setVisible(false);
        });

        baro.set('timeInterval', null);
        baro.draw();

      } else {
        // Update the baro scale when the map has been zoomed/moved.
        map.on('moveend', update_baro_scale_on_moveend);

        if (!window.fixCalcService.get('isRunning')) {
          // enable mouse hovering
          map_hover_handler.set('hover_enabled', true);
        }

        map.getLayers().getArray().forEach(function(e) {
          if (e.get('name') == 'Contest') e.setVisible(true);
        });
      }
    });
  }

  /**
   * Updates the barogram
   */
  flight_display.update = function() {
    updateBaroScale();
    baro.draw();
  };

  /**
   * Returns the flights collection
   * @return {slFlightCollection}
   */
  flight_display.getFlights = function() {
    return flights;
  };

  /**
   * Set the default time. Used at the tracking page.
   * @param {!Number} time Default time to set
   */
  flight_display.setDefaultTime = function(time) {
    default_time = time;
  };

  flight_display.init();
  return flight_display;
}
<|endoftext|>