/**
 * Piwik - free/libre analytics platform
 *
 * DataTable UI class for JqplotGraph.
 *
 * @link http://www.jqplot.com
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
 */

(function ($, require) {

    var exports = require('piwik/UI'),
        DataTable = exports.DataTable,
        dataTablePrototype = DataTable.prototype,
        getLabelFontFamily = function () {
            if (!window.piwik.jqplotLabelFont) {
                window.piwik.jqplotLabelFont = $('<p/>').hide().appendTo('body').css('font-family');
            }

            return window.piwik.jqplotLabelFont || 'Arial';
        }
        ;

    exports.getLabelFontFamily = getLabelFontFamily;

    /**
     * DataTable UI class for jqPlot graph datatable visualizations.
     *
     * @constructor
     */
    exports.JqplotGraphDataTable = function (element) {
        DataTable.call(this, element);
    };

    $.extend(exports.JqplotGraphDataTable.prototype, dataTablePrototype, {

        /**
         * Initializes this class.
         */
        init: function () {
            dataTablePrototype.init.call(this);

            var graphElement = $('.piwik-graph', this.$element);
            if (!graphElement.length) {
                return;
            }

            this._lang = {
                noData: _pk_translate('General_NoDataForGraph'),
                exportTitle: _pk_translate('General_ExportAsImage'),
                exportText: _pk_translate('General_SaveImageOnYourComputer'),
                metricsToPlot: _pk_translate('General_MetricsToPlot'),
                metricToPlot: _pk_translate('General_MetricToPlot'),
                recordsToPlot: _pk_translate('General_RecordsToPlot')
            };

            // set a unique ID for the graph element (required by jqPlot)
            this.targetDivId = this.workingDivId + 'Chart';
            graphElement.attr('id', this.targetDivId);

            try {
                var graphData = JSON.parse(graphElement.attr('data-data'));
            } catch (e) {
                console.error('JSON.parse Error: "' + e + "\" in:\n" + graphElement.attr('data-data'));
                return;
            }

            this.data = graphData.data;
            this._setJqplotParameters(graphData.params);

            if (this.props.display_percentage_in_tooltip) {
                this._setTooltipPercentages();
            }

            this._bindEvents();

            // add external series toggle if it should be added
            if (this.props.external_series_toggle) {
                this.addExternalSeriesToggle(
                    window[this.props.external_series_toggle], // get the function w/ string name
                    this.props.external_series_toggle_show_all == 1
                );
            }

            // render the graph (setTimeout is required, otherwise the graph will not
            // render initially)
            var self = this;
            setTimeout(function () { self.render(); }, 1);
        },

        _setJqplotParameters: function (params) {
            defaultParams = {
                grid: {
                    drawGridLines: false,
                    borderWidth: 0,
                    shadow: false
                },
                title: {
                    show: false
                },
                axesDefaults: {
                    pad: 1.0,
                    tickRenderer: $.jqplot.CanvasAxisTickRenderer,
                    tickOptions: {
                        showMark: false,
                        fontSize: '11px',
                        fontFamily: getLabelFontFamily()
                    },
                    rendererOptions: {
                        drawBaseline: false
                    }
                },
                axes: {
                    yaxis: {
                        tickOptions: {
                            formatString: '%s',
                            formatter: $.jqplot.NumberFormatter
                        }
                    }
                }
            };

            this.jqplotParams = $.extend(true, {}, defaultParams, params);

            this._setColors();
        },

        _setTooltipPercentages: function () {
            this.tooltip = {percentages: []};

            for (var seriesIdx = 0; seriesIdx != this.data.length; ++seriesIdx) {
                var series = this.data[seriesIdx];
                var sum = 0;

                $.each(series, function(index, value) {
                    if ($.isArray(value) && value[1]) {
                        sum = sum + value[1];
                    } else if (!$.isArray(value)) {
                        sum = sum + value;
                    }
                });

                var percentages = this.tooltip.percentages[seriesIdx] = [];
                for (var valueIdx = 0; valueIdx != series.length; ++valueIdx) {
                    var value = series[valueIdx];
                    if ($.isArray(value) && value[1]) {
                        value = value[1];
                    }

                    percentages[valueIdx] = sum > 0 ? Math.round(100 * value / sum) : 0;
                }
            }
        },

        _bindEvents: function () {
            var self = this;
            var target = $('#' + this.targetDivId);

            // tooltip show/hide
            target.on('jqplotDataHighlight', function (e, seriesIndex, valueIndex) {
                self._showDataPointTooltip(this, seriesIndex, valueIndex);
            })
            .on('jqplotDataUnhighlight', function () {
                self._destroyDataPointTooltip($(this));
            });

            // handle window resize
            this._plotWidth = target.innerWidth();
            target.on('resizeGraph', function () { // TODO: shouldn't be a triggerable event.
                self._resizeGraph();
            });

            // export as image
            target.on('piwikExportAsImage', function () {
                self.exportAsImage(target, self._lang);
            });

            // manage resources
            target.on('piwikDestroyPlot', function () {
                if (self._resizeListener) {
                    $(window).off('resize', self._resizeListener);
                }
                self._plot.destroy();
                for (var i = 0; i < $.jqplot.visiblePlots.length; i++) {
                    if ($.jqplot.visiblePlots[i] === self) {
                        $.jqplot.visiblePlots[i] = null;
                    }
                }
            });

            this.$element.closest('.widgetContent').on('widget:resize', function () {
                self._resizeGraph();
            });
        },

        _resizeGraph: function () {
            var width = $('#' + this.targetDivId).innerWidth();
            if (width > 0 && Math.abs(this._plotWidth - width) >= 5) {
                this._plotWidth = width;
                this.render();
            }
        },

        _setWindowResizeListener: function () {
            var self = this;

            var timeout = false;
            this._resizeListener = function () {
                if (timeout) {
                    window.clearTimeout(timeout);
                }

                timeout = window.setTimeout(function () { $('#' + self.targetDivId).trigger('resizeGraph'); }, 300);
            };
            $(window).on('resize', this._resizeListener);
        },

        _destroyDataPointTooltip: function ($element) {
            if ($element.is( ":data('ui-tooltip')" )) {
                $element.tooltip('destroy');
            }
        },

        _showDataPointTooltip: function (element, seriesIndex, valueIndex) {
            // empty
        },

        changeSeries: function (columns, rows) {
            this.showLoading();

            columns = columns || [];
            if (typeof columns == 'string') {
                columns = columns.split(',');
            }

            rows = rows || [];
            if (typeof rows == 'string') {
                rows = rows.split(',');
            }

            var dataTable = $('#' + this.workingDivId).data('uiControlObject');
            dataTable.param.columns = columns.join(',');
            dataTable.param.rows = rows.join(',');
            delete dataTable.param.filter_limit;
            delete dataTable.param.totalRows;
            if (dataTable.param.filter_sort_column != 'label') {
                dataTable.param.filter_sort_column = columns[0];
            }
            dataTable.param.disable_generic_filters = '0';
            dataTable.reloadAjaxDataTable(false);
        },

        destroyPlot: function () {
            var target = $('#' + this.targetDivId);

            target.trigger('piwikDestroyPlot');
            if (target.data('oldHeight') > 0) {
                // handle replot after empty report
                target.height(target.data('oldHeight'));
                target.data('oldHeight', 0);
                target.innerHTML = '';
            }
        },

        showLoading: function () {
            var target = $('#' + this.targetDivId);

            var loading = $(document.createElement('div')).addClass('jqplot-loading');
            loading.css({
                width: target.innerWidth() + 'px',
                height: target.innerHeight() + 'px',
                opacity: 0
            });
            target.prepend(loading);
            loading.css({opacity: .7});
        },

        /**
         * This method sums up total width of all tick according to currently
         * set font-family, font-size and font-weight. It is achieved by
         * creating span elements with ticks and adding their width.
         * Rendered ticks have to be visible to get their real width. But it
         * is too fast for user to notice it. If total ticks width is bigger
         * than container width then half of ticks is being cut out and their
         * width is tested again. Until their total width is smaller than chart
         * div. There is a failsafe so check will be performed no more than 20
         * times, which is I think more than enough. Each tick have its own
         * gutter, by default width of 5 px from each side so they are more
         * readable.
         *
         * @param $targetDiv
         * @private
         */
        _checkTicksWidth: function($targetDiv){
            if(typeof this.jqplotParams.axes.xaxis.ticksOriginal === 'undefined' || this.jqplotParams.axes.xaxis.ticksOriginal === {}){
                this.jqplotParams.axes.xaxis.ticksOriginal = this.jqplotParams.axes.xaxis.ticks.slice();
            }

            var ticks = this.jqplotParams.axes.xaxis.ticks = this.jqplotParams.axes.xaxis.ticksOriginal.slice();

            var divWidth = $targetDiv.width();
            var tickOptions = $.extend(true, {}, this.jqplotParams.axesDefaults.tickOptions, this.jqplotParams.axes.xaxis.tickOptions);
            var gutter = tickOptions.gutter || 5;
            var sumWidthOfTicks = Number.MAX_VALUE;
            var $labelTestChamber = {};
            var tick = "";
            var $body = $("body");
            var maxRunsFailsafe = 20;
            var ticksCount = 0;
            var key = 0;

            while(sumWidthOfTicks > divWidth && maxRunsFailsafe > 0) {
                sumWidthOfTicks = 0;
                for (key = 0; key < ticks.length; key++) {
                    tick = ticks[key];
                    if (tick !== " " && tick !== "") {
                        $labelTestChamber = $("<span/>", {
                            style: 'font-size: ' + (tickOptions.fontSize || '11px') + '; font-family: ' + (tickOptions.fontFamily || 'Arial, Helvetica, sans-serif') + ';' + (tickOptions.fontWeight || 'normal') + ';' + 'clear: both; float: none;',
                            text: tick
                        }).appendTo($body);
                        sumWidthOfTicks += ($labelTestChamber.width() + gutter*2);
                        $labelTestChamber.remove();
                    }
                }

                ticksCount = 0;
                if (sumWidthOfTicks > divWidth) {
                    for (key = 0; key < ticks.length; key++) {
                        tick = ticks[key];
                        if (tick !== " " && tick !== "") {
                            if (ticksCount % 2 == 1) {
                                ticks[key] = " ";
                            }
                            ticksCount++;
                        }
                    }
                }
                maxRunsFailsafe--;
            }
        },

        /** Generic render function */
        render: function () {
            if (this.data.length == 0) { // sanity check
                return;
            }

            var targetDivId = this.workingDivId + 'Chart';
            var lang = this._lang;
            var dataTableDiv = $('#' + this.workingDivId);

            // if the plot has already been rendered, get rid of the existing plot
            var target = $('#' + targetDivId);
            if (target.find('canvas').length > 0) {
                this.destroyPlot();
            }

            // handle replot
            // this has be bound before the check for an empty graph.
            // otherwise clicking on sparklines won't work anymore after an empty
            // report has been displayed.
            var self = this;

            // before drawing a jqplot chart, check if all labels ticks will fit
            // into it
            if( this.param.viewDataTable === "graphBar"
                || this.param.viewDataTable === "graphVerticalBar"
                || this.param.viewDataTable === "graphEvolution" ) {
                self._checkTicksWidth(target);
            }

            // create jqplot chart
            try {
                var plot = self._plot = $.jqplot(targetDivId, this.data, this.jqplotParams);
            } catch (e) {
                // this is thrown when refreshing piwik in the browser
                if (e != "No plot target specified") {
                    throw e;
                }
            }

            self._setWindowResizeListener();

            var self = this;

            // TODO: this code destroys plots when a page is switched. there must be a better way of managing memory.
            if (typeof $.jqplot.visiblePlots == 'undefined') {
                $.jqplot.visiblePlots = [];
                var $rootScope = piwikHelper.getAngularDependency('$rootScope');

                $rootScope.$on('piwikPageChange', function () {
                    for (var i = 0; i < $.jqplot.visiblePlots.length; i++) {
                        if ($.jqplot.visiblePlots[i] == null) {
                            continue;
                        }
                        $.jqplot.visiblePlots[i].destroyPlot();
                    }
                    $.jqplot.visiblePlots = [];
                });
            }

            if (typeof plot != 'undefined') {
                $.jqplot.visiblePlots.push(self);
            }
        },

        /** Export the chart as an image */
        exportAsImage: function (container, lang) {
            var pixelRatio = window.devicePixelRatio || 1;
            var exportCanvas = document.createElement('canvas');
            exportCanvas.width = Math.round(container.width() * pixelRatio);
            exportCanvas.height = Math.round(container.height() * pixelRatio);

            if (!exportCanvas.getContext) {
                alert("Sorry, not supported in your browser. Please upgrade your browser :)");
                return;
            }
            var exportCtx = exportCanvas.getContext('2d');

            var canvases = container.find('canvas');

            for (var i = 0; i < canvases.length; i++) {
                var canvas = canvases.eq(i);
                var position = canvas.position();
                var parent = canvas.parent();
                if (parent.hasClass('jqplot-axis')) {
                    var addPosition = parent.position();
                    position.left += addPosition.left;
                    position.top += addPosition.top + parseInt(parent.css('marginTop'), 10);
                }
                exportCtx.drawImage(canvas[0], Math.round(position.left * pixelRatio), Math.round(position.top * pixelRatio));
            }

            var exported = exportCanvas.toDataURL("image/png");

            var img = document.createElement('img');
            img.src = exported;

            img = $(img).css({
                width: Math.round(exportCanvas.width / pixelRatio) + 'px',
                height: Math.round(exportCanvas.height / pixelRatio) + 'px'
            });

            var popover = $(document.createElement('div'));

            popover.append('<div style="font-size: 13px; margin-bottom: 10px;">'
                + lang.exportText + '</div>').append($(img));

            popover.dialog({
                title: lang.exportTitle,
                modal: true,
                width: 'auto',
                position: ['center', 'center'],
                resizable: false,
                autoOpen: true,
                open: function (event, ui) {
                    $('.ui-widget-overlay').on('click.popover', function () {
                        popover.dialog('close');
                    });
                },
                close: function (event, ui) {
                    $(this).dialog("destroy").remove();
                }
            });
        },

        // ------------------------------------------------------------
        //  HELPER METHODS
        // ------------------------------------------------------------

        /** Generate ticks in y direction */
        setYTicks: function () {
            // default axis
            this.setYTicksForAxis('yaxis', this.jqplotParams.axes.yaxis);
            // other axes: y2axis, y3axis...
            for (var i = 2; typeof this.jqplotParams.axes['y' + i + 'axis'] != 'undefined'; i++) {
                this.setYTicksForAxis('y' + i + 'axis', this.jqplotParams.axes['y' + i + 'axis']);
            }
        },

        setYTicksForAxis: function (axisName, axis) {
            // calculate maximum x value of all data sets
            var maxCrossDataSets = 0;
            for (var i = 0; i < this.data.length; i++) {
                if (this.jqplotParams.series[i].yaxis == axisName) {
                    var maxValue = Math.max.apply(Math, this.data[i]);
                    if (maxValue > maxCrossDataSets) {
                        maxCrossDataSets = maxValue;
                    }
                    maxCrossDataSets = parseFloat(maxCrossDataSets);
                }
            }

            // add little padding on top
            maxCrossDataSets += Math.max(1, Math.round(maxCrossDataSets * .03));

            // round to the nearest multiple of ten
            if (maxCrossDataSets > 15) {
                maxCrossDataSets = maxCrossDataSets + 10 - maxCrossDataSets % 10;
            }

            if (maxCrossDataSets == 0) {
                maxCrossDataSets = 1;
            }

            // make sure percent axes don't go above 100%
            if (axis.tickOptions.formatString.substring(2, 3) == '%' && maxCrossDataSets > 100) {
                maxCrossDataSets = 100;
            }

            // calculate y-values for ticks
            var ticks = [];
            var numberOfTicks = 2;
            var tickDistance = Math.ceil(maxCrossDataSets / numberOfTicks);
            for (var i = 0; i <= numberOfTicks; i++) {
                ticks.push(i * tickDistance);
            }
            axis.ticks = ticks;
        },

        /** Get a formatted y values (with unit) */
        formatY: function (value, seriesIndex) {
            var floatVal = parseFloat(value);
            var intVal = parseInt(value, 10);
            if (Math.abs(floatVal - intVal) >= 0.005) {
                value = Math.round(floatVal * 100) / 100;
            } else if (parseFloat(intVal) == floatVal) {
                value = intVal;
            } else {
                value = floatVal;
            }

            var axisId = this.jqplotParams.series[seriesIndex].yaxis;
            var formatString = this.jqplotParams.axes[axisId].tickOptions.formatString;

            return $.jqplot.NumberFormatter(formatString, value);
        },

        /**
         * Add an external series toggle.
         * As opposed to addSeriesPicker, the external series toggle can only show/hide
         * series that are already loaded.
         *
         * @param seriesPickerClass a subclass of JQPlotExternalSeriesToggle
         * @param initiallyShowAll
         */
        addExternalSeriesToggle: function (seriesPickerClass, initiallyShowAll) {
            new seriesPickerClass(this.targetDivId, this, initiallyShowAll);

            if (!initiallyShowAll) {
                // initially, show only the first series
                this.data = [this.data[0]];
                this.jqplotParams.series = [this.jqplotParams.series[0]];
                this.setYTicks();
            }
        },

        /**
         * Sets the colors used to render this graph.
         */
        _setColors: function () {
            var colorManager = piwik.ColorManager,
                seriesColorNames = ['series1', 'series2', 'series3', 'series4', 'series5',
                                    'series6', 'series7', 'series8', 'series9', 'series10'];

            var viewDataTable = $('#' + this.workingDivId).data('uiControlObject').param['viewDataTable'];

            var graphType;
            if (viewDataTable == 'graphEvolution') {
                graphType = 'evolution';
            } else if (viewDataTable == 'graphPie') {
                graphType = 'pie';
            } else if (viewDataTable == 'graphVerticalBar') {
                graphType = 'bar';
            }

            var namespace = graphType + '-graph-colors';

            this.jqplotParams.seriesColors = colorManager.getColors(namespace, seriesColorNames, true);
            this.jqplotParams.grid.background = colorManager.getColor(namespace, 'grid-background');
            this.jqplotParams.grid.borderColor = colorManager.getColor(namespace, 'grid-border');
            this.tickColor = colorManager.getColor(namespace, 'ticks');
            this.singleMetricColor = colorManager.getColor(namespace, 'single-metric-label')
        }
    });

    DataTable.registerFooterIconHandler('graphPie', DataTable.switchToGraph);
    DataTable.registerFooterIconHandler('graphVerticalBar', DataTable.switchToGraph);
    DataTable.registerFooterIconHandler('graphEvolution', DataTable.switchToGraph);

})(jQuery, require);

// ----------------------------------------------------------------
//  EXTERNAL SERIES TOGGLE
//  Use external dom elements and their events to show/hide series
// ----------------------------------------------------------------

function JQPlotExternalSeriesToggle(targetDivId, jqplotObject, initiallyShowAll) {
    this.init(targetDivId, originalConfig, initiallyShowAll);
}

JQPlotExternalSeriesToggle.prototype = {

    init: function (targetDivId, jqplotObject, initiallyShowAll) {
        this.targetDivId = targetDivId;
        this.jqplotObject = jqplotObject;
        this.originalData = jqplotObject.data;
        this.originalSeries = jqplotObject.jqplotParams.series;
        this.originalAxes = jqplotObject.jqplotParams.axes;
        this.originalParams = jqplotObject.jqplotParams;
        this.originalSeriesColors = jqplotObject.jqplotParams.seriesColors;
        this.initiallyShowAll = initiallyShowAll;

        this.activated = [];
        this.target = $('#' + targetDivId);

        this.attachEvents();
    },

    // can be overridden
    attachEvents: function () {},

    // show a single series
    showSeries: function (i) {
        for (var j = 0; j < this.activated.length; j++) {
            this.activated[j] = (i == j);
        }
        this.replot();
    },

    // toggle a series (make plotting multiple series possible)
    toggleSeries: function (i) {
        var activatedCount = 0;
        for (var k = 0; k < this.activated.length; k++) {
            if (this.activated[k]) {
                activatedCount++;
            }
        }
        if (activatedCount == 1 && this.activated[i]) {
            // prevent removing the only visible metric
            return;
        }

        this.activated[i] = !this.activated[i];
        this.replot();
    },

    replot: function () {
        this.beforeReplot();

        // build new config and replot
        var usedAxes = [];
        var config = {data: this.originalData, params: this.originalParams};
        config.data = [];
        config.params.series = [];
        config.params.axes = {xaxis: this.originalAxes.xaxis};
        config.params.seriesColors = [];
        for (var j = 0; j < this.activated.length; j++) {
            if (!this.activated[j]) {
                continue;
            }
            config.data.push(this.originalData[j]);
            config.params.seriesColors.push(this.originalSeriesColors[j]);
            config.params.series.push($.extend(true, {}, this.originalSeries[j]));
            // build array of used axes
            var axis = this.originalSeries[j].yaxis;
            if ($.inArray(axis, usedAxes) == -1) {
                usedAxes.push(axis);
            }
        }

        // build new axes config
        var replaceAxes = {};
        for (j = 0; j < usedAxes.length; j++) {
            var originalAxisName = usedAxes[j];
            var newAxisName = (j == 0 ? 'yaxis' : 'y' + (j + 1) + 'axis');
            replaceAxes[originalAxisName] = newAxisName;
            config.params.axes[newAxisName] = this.originalAxes[originalAxisName];
        }

        // replace axis names in series config
        for (j = 0; j < config.params.series.length; j++) {
            var series = config.params.series[j];
            series.yaxis = replaceAxes[series.yaxis];
        }

        this.jqplotObject.data = config.data;
        this.jqplotObject.jqplotParams = config.params;
        this.jqplotObject.setYTicks();
        this.jqplotObject.render();
    },

    // can be overridden
    beforeReplot: function () {}

};

// ROW EVOLUTION SERIES TOGGLE

function RowEvolutionSeriesToggle(targetDivId, jqplotData, initiallyShowAll) {
    this.init(targetDivId, jqplotData, initiallyShowAll);
}

RowEvolutionSeriesToggle.prototype = JQPlotExternalSeriesToggle.prototype;

RowEvolutionSeriesToggle.prototype.attachEvents = function () {
    var self = this;
    this.seriesPickers = this.target.closest('.rowevolution').find('table.metrics tr');

    this.seriesPickers.each(function (i) {
        var el = $(this);
        el.off('click').on('click', function (e) {
            if (e.shiftKey) {
                self.toggleSeries(i);

                document.getSelection().removeAllRanges(); // make sure chrome doesn't select text
            } else {
                self.showSeries(i);
            }
            return false;
        });

        if (i == 0 || self.initiallyShowAll) {
            // show the active series
            // if initiallyShowAll, all are active; otherwise only the first one
            self.activated.push(true);
        } else {
            // fade out the others
            el.find('td').css('opacity', .5);
            self.activated.push(false);
        }

        // prevent selecting in ie & opera (they don't support doing this via css)
        if ($.browser.msie) {
            this.ondrag = function () { return false; };
            this.onselectstart = function () { return false; };
        } else if ($.browser.opera) {
            $(this).attr('unselectable', 'on');
        }
    });
};

RowEvolutionSeriesToggle.prototype.beforeReplot = function () {
    // fade out if not activated
    for (var i = 0; i < this.activated.length; i++) {
        if (this.activated[i]) {
            this.seriesPickers.eq(i).find('td').css('opacity', 1);
        } else {
            this.seriesPickers.eq(i).find('td').css('opacity', .5);
        }
    }
};

// ------------------------------------------------------------
//  PIWIK NUMBERFORMATTER PLUGIN FOR JQPLOT
// ------------------------------------------------------------
(function($){

    $.jqplot.NumberFormatter = function (format, value) {

        if (!$.isNumeric(value)) {
            return format.replace(/%s/, value);
        }
        return format.replace(/%s/, NumberFormatter.formatNumber(value));
    }

})(jQuery);


// ------------------------------------------------------------
//  PIWIK TICKS PLUGIN FOR JQPLOT
//  Handle ticks the piwik way...
// ------------------------------------------------------------

(function ($) {

    $.jqplot.PiwikTicks = function (options) {
        // canvas for the grid
        this.piwikTicksCanvas = null;
        // canvas for the highlight
        this.piwikHighlightCanvas = null;
        // renderer used to draw the marker of the highlighted point
        this.markerRenderer = new $.jqplot.MarkerRenderer({
            shadow: false
        });
        // the x tick the mouse is over
        this.currentXTick = false;
        // show the highlight around markers
        this.showHighlight = false;
        // show the grid
        this.showGrid = false;
        // show the ticks
        this.showTicks = false;

        $.extend(true, this, options);
    };

    $.jqplot.PiwikTicks.init = function (target, data, opts) {
        // add plugin as an attribute to the plot
        var options = opts || {};
        this.plugins.piwikTicks = new $.jqplot.PiwikTicks(options.piwikTicks);

        if (typeof $.jqplot.PiwikTicks.init.eventsBound == 'undefined') {
            $.jqplot.PiwikTicks.init.eventsBound = true;
            $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMouseMove]);
            $.jqplot.eventListenerHooks.push(['jqplotMouseLeave', handleMouseLeave]);
        }
    };

    // draw the grid
    // called with context of plot
    $.jqplot.PiwikTicks.postDraw = function () {
        var c = this.plugins.piwikTicks;

        // highligh canvas
        if (c.showHighlight) {
            c.piwikHighlightCanvas = new $.jqplot.GenericCanvas();

            this.eventCanvas._elem.before(c.piwikHighlightCanvas.createElement(
                this._gridPadding, 'jqplot-piwik-highlight-canvas', this._plotDimensions, this));
            c.piwikHighlightCanvas.setContext();
        }

        // grid canvas
        if (c.showTicks) {
            var dimensions = this._plotDimensions;
            dimensions.height += 6;
            c.piwikTicksCanvas = new $.jqplot.GenericCanvas();
            this.series[0].shadowCanvas._elem.before(c.piwikTicksCanvas.createElement(
                this._gridPadding, 'jqplot-piwik-ticks-canvas', dimensions, this));
            c.piwikTicksCanvas.setContext();

            var ctx = c.piwikTicksCanvas._ctx;

            var ticks = this.data[0];
            var totalWidth = ctx.canvas.width;
            var tickWidth = totalWidth / ticks.length;

            var xaxisLabels = this.axes.xaxis.ticks;

            for (var i = 0; i < ticks.length; i++) {
                var pos = Math.round(i * tickWidth + tickWidth / 2);
                var full = xaxisLabels[i] && xaxisLabels[i] != ' ';
                drawLine(ctx, pos, full, c.showGrid, c.tickColor);
            }
        }
    };

    $.jqplot.preInitHooks.push($.jqplot.PiwikTicks.init);
    $.jqplot.postDrawHooks.push($.jqplot.PiwikTicks.postDraw);

    // draw a 1px line
    function drawLine(ctx, x, full, showGrid, color) {
        ctx.save();
        ctx.strokeStyle = color;

        ctx.beginPath();
        ctx.lineWidth = 2;
        var top = 0;
        if ((full && !showGrid) || !full) {
            top = ctx.canvas.height - 5;
        }
        ctx.moveTo(x, top);
        ctx.lineTo(x, full ? ctx.canvas.height : ctx.canvas.height - 2);
        ctx.stroke();

        // canvas renders line slightly too large
        ctx.clearRect(x, 0, x + 1, ctx.canvas.height);

        ctx.restore();
    }

    // tigger the event jqplotPiwikTickOver when the mosue enters
    // and new tick. this is used for tooltips.
    function handleMouseMove(ev, gridpos, datapos, neighbor, plot) {
        var c = plot.plugins.piwikTicks;

        var tick = Math.floor(datapos.xaxis + 0.5) - 1;
        if (tick !== c.currentXTick) {
            c.currentXTick = tick;
            plot.target.trigger('jqplotPiwikTickOver', [tick]);
            highlight(plot, tick);
        }
    }

    function handleMouseLeave(ev, gridpos, datapos, neighbor, plot) {
        unHighlight(plot);
        plot.plugins.piwikTicks.currentXTick = false;
    }

    // highlight a marker
    function highlight(plot, tick) {
        var c = plot.plugins.piwikTicks;

        if (!c.showHighlight) {
            return;
        }

        unHighlight(plot);

        for (var i = 0; i < plot.series.length; i++) {
            var series = plot.series[i];
            var seriesMarkerRenderer = series.markerRenderer;

            c.markerRenderer.style = seriesMarkerRenderer.style;
            c.markerRenderer.size = seriesMarkerRenderer.size + 5;

            var rgba = $.jqplot.getColorComponents(seriesMarkerRenderer.color);
            var newrgb = [rgba[0], rgba[1], rgba[2]];
            var alpha = rgba[3] * .4;
            c.markerRenderer.color = 'rgba(' + newrgb[0] + ',' + newrgb[1] + ',' + newrgb[2] + ',' + alpha + ')';
            c.markerRenderer.init();

            var position = series.gridData[tick];
            c.markerRenderer.draw(position[0], position[1], c.piwikHighlightCanvas._ctx);
        }
    }

    function unHighlight(plot) {
        var canvas = plot.plugins.piwikTicks.piwikHighlightCanvas;
        if (canvas !== null) {
            var ctx = canvas._ctx;
            ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
        }
    }

})(jQuery);

// ------------------------------------------------------------
//  LEGEND PLUGIN FOR JQPLOT
//  Render legend on canvas
// ------------------------------------------------------------

(function ($) {

    $.jqplot.CanvasLegendRenderer = function (options) {
        // canvas for the legend
        this.legendCanvas = null;
        // is it a legend for a single metric only (pie chart)?
        this.singleMetric = false;
        // render the legend?
        this.show = false;

        $.extend(true, this, options);
    };

    $.jqplot.CanvasLegendRenderer.init = function (target, data, opts) {
        // add plugin as an attribute to the plot
        var options = opts || {};
        this.plugins.canvasLegend = new $.jqplot.CanvasLegendRenderer(options.canvasLegend);

        // add padding above the grid
        // legend will be put there
        if (this.plugins.canvasLegend.show) {
            options.gridPadding = {
                top: 21, right: 0
            };
        }

    };

    // render the legend
    $.jqplot.CanvasLegendRenderer.postDraw = function () {
        var plot = this;
        var legend = plot.plugins.canvasLegend;

        if (!legend.show) {
            return;
        }

        // initialize legend canvas
        var padding = {top: 0, right: this._gridPadding.right, bottom: 0, left: this._gridPadding.left};
        var dimensions = {width: this._plotDimensions.width, height: this._gridPadding.top};
        var width = this._plotDimensions.width - this._gridPadding.left - this._gridPadding.right;

        legend.legendCanvas = new $.jqplot.GenericCanvas();
        this.eventCanvas._elem.before(legend.legendCanvas.createElement(
            padding, 'jqplot-legend-canvas', dimensions, plot));
        legend.legendCanvas.setContext();

        var ctx = legend.legendCanvas._ctx;
        ctx.save();
        ctx.font = '11px ' + require('piwik/UI').getLabelFontFamily()

        // render series names
        var x = 0;
        var series = plot.legend._series;
        for (var i = 0; i < series.length; i++) {
            var s = series[i];
            var label;
            if (legend.labels && legend.labels[i]) {
                label = legend.labels[i];
            } else {
                label = s.label.toString();
            }

            ctx.fillStyle = s.color;
            if (legend.singleMetric) {
                ctx.fillStyle = legend.singleMetricColor;
            }

            ctx.fillRect(x, 10, 10, 2);
            x += 15;

            var nextX = x + ctx.measureText(label).width + 20;

            if (nextX + 70 > width) {
                ctx.fillText("[...]", x, 15);
                x += ctx.measureText("[...]").width + 20;
                break;
            }

            ctx.fillText(label, x, 15);
            x = nextX;
        }

        legend.width = x;

        ctx.restore();
    };

    $.jqplot.preInitHooks.push($.jqplot.CanvasLegendRenderer.init);
    $.jqplot.postDrawHooks.push($.jqplot.CanvasLegendRenderer.postDraw);

})(jQuery);

// ------------------------------------------------------------
//  SERIES PICKER
// ------------------------------------------------------------

(function ($, require) {
    $.jqplot.preInitHooks.push(function (target, data, options) {
        // create the series picker
        var dataTable = $('#' + target).closest('.dataTable').data('uiControlObject');
        if (!dataTable) { // if we're not dealing w/ a DataTable visualization, don't add the series picker
            return;
        }

        var SeriesPicker = require('piwik/DataTableVisualizations/Widgets').SeriesPicker;
        var seriesPicker = new SeriesPicker(dataTable);

        // handle placeSeriesPicker event
        var plot = this;
        $(seriesPicker).bind('placeSeriesPicker', function () {
            this.domElem.css('margin-left', (plot._gridPadding.left + plot.plugins.canvasLegend.width - 1) + 'px');
            plot.baseCanvas._elem.before(this.domElem);
        });

        // handle seriesPicked event
        $(seriesPicker).bind('seriesPicked', function (e, columns, rows) {
            dataTable.changeSeries(columns, rows);
        });

        this.plugins.seriesPicker = seriesPicker;
    });

    $.jqplot.postDrawHooks.push(function () {
        this.plugins.seriesPicker.init();
    });
})(jQuery, require);

// ------------------------------------------------------------
//  PIE CHART LEGEND PLUGIN FOR JQPLOT
//  Render legend inside the pie graph
// ------------------------------------------------------------

(function ($) {

    $.jqplot.PieLegend = function (options) {
        // canvas for the legend
        this.pieLegendCanvas = null;
        // render the legend?
        this.show = false;

        $.extend(true, this, options);
    };

    $.jqplot.PieLegend.init = function (target, data, opts) {
        // add plugin as an attribute to the plot
        var options = opts || {};
        this.plugins.pieLegend = new $.jqplot.PieLegend(options.pieLegend);
    };

    // render the legend
    $.jqplot.PieLegend.postDraw = function () {
        var plot = this;
        var legend = plot.plugins.pieLegend;

        if (!legend.show) {
            return;
        }

        var series = plot.series[0];
        var angles = series._sliceAngles;
        var radius = series._diameter / 2;
        var center = series._center;
        var colors = this.seriesColors;

        // concentric line angles
        var lineAngles = [];
        for (var i = 0; i < angles.length; i++) {
            lineAngles.push((angles[i][0] + angles[i][1]) / 2 + Math.PI / 2);
        }

        // labels
        var labels = [];
        var data = series._plotData;
        for (i = 0; i < data.length; i++) {
            labels.push(data[i][0]);
        }

        // initialize legend canvas
        legend.pieLegendCanvas = new $.jqplot.GenericCanvas();
        plot.series[0].canvas._elem.before(legend.pieLegendCanvas.createElement(
            plot._gridPadding, 'jqplot-pie-legend-canvas', plot._plotDimensions, plot));
        legend.pieLegendCanvas.setContext();

        var ctx = legend.pieLegendCanvas._ctx;
        ctx.save();

        ctx.font = '11px ' + require('piwik/UI').getLabelFontFamily()

        // render labels
        var height = legend.pieLegendCanvas._elem.height();
        var x1, x2, y1, y2, lastY2 = false, right, lastRight = false;
        for (i = 0; i < labels.length; i++) {
            var label = labels[i];

            ctx.strokeStyle = colors[i % colors.length];
            ctx.lineCap = 'round';
            ctx.lineWidth = 1;

            // concentric line
            x1 = center[0] + Math.sin(lineAngles[i]) * (radius);
            y1 = center[1] - Math.cos(lineAngles[i]) * (radius);

            x2 = center[0] + Math.sin(lineAngles[i]) * (radius + 7);
            y2 = center[1] - Math.cos(lineAngles[i]) * (radius + 7);

            right = x2 > center[0];

            // move close labels
            if (lastY2 !== false && lastRight == right && (
                (right && y2 - lastY2 < 13) ||
                    (!right && lastY2 - y2 < 13))) {

                if (x1 > center[0]) {
                    // move down if the label is in the right half of the graph
                    y2 = lastY2 + 13;
                } else {
                    // move up if in left halt
                    y2 = lastY2 - 13;
                }
            }

            if (y2 < 4 || y2 + 4 > height) {
                continue;
            }

            ctx.beginPath();
            ctx.moveTo(x1, y1);
            ctx.lineTo(x2, y2);

            ctx.closePath();
            ctx.stroke();

            // horizontal line
            ctx.beginPath();
            ctx.moveTo(x2, y2);
            if (right) {
                ctx.lineTo(x2 + 5, y2);
            } else {
                ctx.lineTo(x2 - 5, y2);
            }

            ctx.closePath();
            ctx.stroke();

            lastY2 = y2;
            lastRight = right;

            // text
            if (right) {
                var x = x2 + 9;
            } else {
                var x = x2 - 9 - ctx.measureText(label).width;
            }

            ctx.fillStyle = legend.labelColor;
            ctx.fillText(label, x, y2 + 3);
        }

        ctx.restore();
    };

    $.jqplot.preInitHooks.push($.jqplot.PieLegend.init);
    $.jqplot.postDrawHooks.push($.jqplot.PieLegend.postDraw);

})(jQuery, require);
