(function ($) {
    $.color = {};
    $.color.make = function (r, g, b, a) {
        var o = {};
        o.r = r || 0;
        o.g = g || 0;
        o.b = b || 0;
        o.a = a != null ? a : 1;
        o.add = function (c, d) {
            for (var i = 0; i < c.length; ++i)o[c.charAt(i)] += d;
            return o.normalize()
        };
        o.scale = function (c, f) {
            for (var i = 0; i < c.length; ++i)o[c.charAt(i)] *= f;
            return o.normalize()
        };
        o.toString = function () {
            if (o.a >= 1) {
                return"rgb(" + [o.r, o.g, o.b].join(",") + ")"
            } else {
                return"rgba(" + [o.r, o.g, o.b, o.a].join(",") + ")"
            }
        };
        o.normalize = function () {
            function clamp(min, value, max) {
                return value < min ? min : value > max ? max : value
            }

            o.r = clamp(0, parseInt(o.r), 255);
            o.g = clamp(0, parseInt(o.g), 255);
            o.b = clamp(0, parseInt(o.b), 255);
            o.a = clamp(0, o.a, 1);
            return o
        };
        o.clone = function () {
            return $.color.make(o.r, o.b, o.g, o.a)
        };
        return o.normalize()
    };
    $.color.extract = function (elem, css) {
        var c;
        do {
            c = elem.css(css).toLowerCase();
            if (c != "" && c != "transparent")break;
            elem = elem.parent()
        } while (elem.length && !$.nodeName(elem.get(0), "body"));
        if (c == "rgba(0, 0, 0, 0)")c = "transparent";
        return $.color.parse(c)
    };
    $.color.parse = function (str) {
        var res, m = $.color.make;
        if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10));
        if (res = /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4]));
        if (res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55);
        if (res = /rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55, parseFloat(res[4]));
        if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16));
        if (res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1] + res[1], 16), parseInt(res[2] + res[2], 16), parseInt(res[3] + res[3], 16));
        var name = $.trim(str).toLowerCase();
        if (name == "transparent")return m(255, 255, 255, 0); else {
            res = lookupColors[name] || [0, 0, 0];
            return m(res[0], res[1], res[2])
        }
    };
    var lookupColors = {aqua: [0, 255, 255], azure: [240, 255, 255], beige: [245, 245, 220], black: [0, 0, 0], blue: [0, 0, 255], brown: [165, 42, 42], cyan: [0, 255, 255], darkblue: [0, 0, 139], darkcyan: [0, 139, 139], darkgrey: [169, 169, 169], darkgreen: [0, 100, 0], darkkhaki: [189, 183, 107], darkmagenta: [139, 0, 139], darkolivegreen: [85, 107, 47], darkorange: [255, 140, 0], darkorchid: [153, 50, 204], darkred: [139, 0, 0], darksalmon: [233, 150, 122], darkviolet: [148, 0, 211], fuchsia: [255, 0, 255], gold: [255, 215, 0], green: [0, 128, 0], indigo: [75, 0, 130], khaki: [240, 230, 140], lightblue: [173, 216, 230], lightcyan: [224, 255, 255], lightgreen: [144, 238, 144], lightgrey: [211, 211, 211], lightpink: [255, 182, 193], lightyellow: [255, 255, 224], lime: [0, 255, 0], magenta: [255, 0, 255], maroon: [128, 0, 0], navy: [0, 0, 128], olive: [128, 128, 0], orange: [255, 165, 0], pink: [255, 192, 203], purple: [128, 0, 128], violet: [128, 0, 128], red: [255, 0, 0], silver: [192, 192, 192], white: [255, 255, 255], yellow: [255, 255, 0]}
})(jQuery);
(function ($) {
    var hasOwnProperty = Object.prototype.hasOwnProperty;

    function Canvas(cls, container) {
        var element = container.children("." + cls)[0];
        if (element == null) {
            element = document.createElement("canvas");
            element.className = cls;
            $(element).css({direction: "ltr", position: "absolute", left: 0, top: 0}).appendTo(container);
            if (!element.getContext) {
                if (window.G_vmlCanvasManager) {
                    element = window.G_vmlCanvasManager.initElement(element)
                } else {
                    throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.")
                }
            }
        }
        this.element = element;
        var context = this.context = element.getContext("2d");
        var devicePixelRatio = window.devicePixelRatio || 1, backingStoreRatio = context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1;
        this.pixelRatio = devicePixelRatio / backingStoreRatio;
        this.resize(container.width(), container.height());
        this.textContainer = null;
        this.text = {};
        this._textCache = {}
    }

    Canvas.prototype.resize = function (width, height) {
        if (width <= 0 || height <= 0) {
            throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height)
        }
        var element = this.element, context = this.context, pixelRatio = this.pixelRatio;
        if (this.width != width) {
            element.width = width * pixelRatio;
            element.style.width = width + "px";
            this.width = width
        }
        if (this.height != height) {
            element.height = height * pixelRatio;
            element.style.height = height + "px";
            this.height = height
        }
        context.restore();
        context.save();
        context.scale(pixelRatio, pixelRatio)
    };
    Canvas.prototype.clear = function () {
        this.context.clearRect(0, 0, this.width, this.height)
    };
    Canvas.prototype.render = function () {
        var cache = this._textCache;
        for (var layerKey in cache) {
            if (hasOwnProperty.call(cache, layerKey)) {
                var layer = this.getTextLayer(layerKey), layerCache = cache[layerKey];
                layer.hide();
                for (var styleKey in layerCache) {
                    if (hasOwnProperty.call(layerCache, styleKey)) {
                        var styleCache = layerCache[styleKey];
                        for (var key in styleCache) {
                            if (hasOwnProperty.call(styleCache, key)) {
                                var positions = styleCache[key].positions;
                                for (var i = 0, position; position = positions[i]; i++) {
                                    if (position.active) {
                                        if (!position.rendered) {
                                            layer.append(position.element);
                                            position.rendered = true
                                        }
                                    } else {
                                        positions.splice(i--, 1);
                                        if (position.rendered) {
                                            position.element.detach()
                                        }
                                    }
                                }
                                if (positions.length == 0) {
                                    delete styleCache[key]
                                }
                            }
                        }
                    }
                }
                layer.show()
            }
        }
    };
    Canvas.prototype.getTextLayer = function (classes) {
        var layer = this.text[classes];
        if (layer == null) {
            if (this.textContainer == null) {
                this.textContainer = $("<div class='flot-text'></div>").css({position: "absolute", top: 0, left: 0, bottom: 0, right: 0, "font-size": "smaller", color: "#545454"}).insertAfter(this.element)
            }
            layer = this.text[classes] = $("<div></div>").addClass(classes).css({position: "absolute", top: 0, left: 0, bottom: 0, right: 0}).appendTo(this.textContainer)
        }
        return layer
    };
    Canvas.prototype.getTextInfo = function (layer, text, font, angle, width) {
        var textStyle, layerCache, styleCache, info;
        text = "" + text;
        if (typeof font === "object") {
            textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family
        } else {
            textStyle = font
        }
        layerCache = this._textCache[layer];
        if (layerCache == null) {
            layerCache = this._textCache[layer] = {}
        }
        styleCache = layerCache[textStyle];
        if (styleCache == null) {
            styleCache = layerCache[textStyle] = {}
        }
        info = styleCache[text];
        if (info == null) {
            var element = $("<div></div>").html(text).css({position: "absolute", "max-width": width, top: -9999}).appendTo(this.getTextLayer(layer));
            if (typeof font === "object") {
                element.css({font: textStyle, color: font.color})
            } else if (typeof font === "string") {
                element.addClass(font)
            }
            info = styleCache[text] = {width: element.outerWidth(true), height: element.outerHeight(true), element: element, positions: []};
            element.detach()
        }
        return info
    };
    Canvas.prototype.addText = function (layer, x, y, text, font, angle, width, halign, valign) {
        var info = this.getTextInfo(layer, text, font, angle, width), positions = info.positions;
        if (halign == "center") {
            x -= info.width / 2
        } else if (halign == "right") {
            x -= info.width
        }
        if (valign == "middle") {
            y -= info.height / 2
        } else if (valign == "bottom") {
            y -= info.height
        }
        for (var i = 0, position; position = positions[i]; i++) {
            if (position.x == x && position.y == y) {
                position.active = true;
                return
            }
        }
        position = {active: true, rendered: false, element: positions.length ? info.element.clone() : info.element, x: x, y: y};
        positions.push(position);
        position.element.css({top: Math.round(y), left: Math.round(x), "text-align": halign})
    };
    Canvas.prototype.removeText = function (layer, x, y, text, font, angle) {
        if (text == null) {
            var layerCache = this._textCache[layer];
            if (layerCache != null) {
                for (var styleKey in layerCache) {
                    if (hasOwnProperty.call(layerCache, styleKey)) {
                        var styleCache = layerCache[styleKey];
                        for (var key in styleCache) {
                            if (hasOwnProperty.call(styleCache, key)) {
                                var positions = styleCache[key].positions;
                                for (var i = 0, position; position = positions[i]; i++) {
                                    position.active = false
                                }
                            }
                        }
                    }
                }
            }
        } else {
            var positions = this.getTextInfo(layer, text, font, angle).positions;
            for (var i = 0, position; position = positions[i]; i++) {
                if (position.x == x && position.y == y) {
                    position.active = false
                }
            }
        }
    };
    function Plot(placeholder, data_, options_, plugins) {
        var series = [], options = {colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"], legend: {show: true, noColumns: 1, labelFormatter: null, labelBoxBorderColor: "#ccc", container: null, position: "ne", margin: 5, backgroundColor: null, backgroundOpacity: .85, sorted: null}, xaxis: {show: null, position: "bottom", mode: null, font: null, color: null, tickColor: null, transform: null, inverseTransform: null, min: null, max: null, autoscaleMargin: null, ticks: null, tickFormatter: null, labelWidth: null, labelHeight: null, reserveSpace: null, tickLength: null, alignTicksWithAxis: null, tickDecimals: null, tickSize: null, minTickSize: null}, yaxis: {autoscaleMargin: .02, position: "left"}, xaxes: [], yaxes: [], series: {points: {show: false, radius: 3, lineWidth: 2, fill: true, fillColor: "#ffffff", symbol: "circle"}, lines: {lineWidth: 2, fill: false, fillColor: null, steps: false}, bars: {show: false, lineWidth: 2, barWidth: 1, fill: true, fillColor: null, align: "left", horizontal: false, zero: true}, shadowSize: 3, highlightColor: null}, grid: {show: true, aboveData: false, color: "#545454", backgroundColor: null, borderColor: null, tickColor: null, margin: 0, labelMargin: 5, axisMargin: 8, borderWidth: 2, minBorderMargin: null, markings: null, markingsColor: "#f4f4f4", markingsLineWidth: 2, clickable: false, hoverable: false, autoHighlight: true, mouseActiveRadius: 10}, interaction: {redrawOverlayInterval: 1e3 / 60}, hooks: {}}, surface = null, overlay = null, eventHolder = null, ctx = null, octx = null, xaxes = [], yaxes = [], plotOffset = {left: 0, right: 0, top: 0, bottom: 0}, plotWidth = 0, plotHeight = 0, hooks = {processOptions: [], processRawData: [], processDatapoints: [], processOffset: [], drawBackground: [], drawSeries: [], draw: [], bindEvents: [], drawOverlay: [], shutdown: []}, plot = this;
        plot.setData = setData;
        plot.setupGrid = setupGrid;
        plot.draw = draw;
        plot.getPlaceholder = function () {
            return placeholder
        };
        plot.getCanvas = function () {
            return surface.element
        };
        plot.getPlotOffset = function () {
            return plotOffset
        };
        plot.width = function () {
            return plotWidth
        };
        plot.height = function () {
            return plotHeight
        };
        plot.offset = function () {
            var o = eventHolder.offset();
            o.left += plotOffset.left;
            o.top += plotOffset.top;
            return o
        };
        plot.getData = function () {
            return series
        };
        plot.getAxes = function () {
            var res = {}, i;
            $.each(xaxes.concat(yaxes), function (_, axis) {
                if (axis)res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis
            });
            return res
        };
        plot.getXAxes = function () {
            return xaxes
        };
        plot.getYAxes = function () {
            return yaxes
        };
        plot.c2p = canvasToAxisCoords;
        plot.p2c = axisToCanvasCoords;
        plot.getOptions = function () {
            return options
        };
        plot.highlight = highlight;
        plot.unhighlight = unhighlight;
        plot.triggerRedrawOverlay = triggerRedrawOverlay;
        plot.pointOffset = function (point) {
            return{left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10), top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)}
        };
        plot.shutdown = shutdown;
        plot.destroy = function () {
            shutdown();
            placeholder.removeData("plot").empty();
            series = [];
            options = null;
            surface = null;
            overlay = null;
            eventHolder = null;
            ctx = null;
            octx = null;
            xaxes = [];
            yaxes = [];
            hooks = null;
            highlights = [];
            plot = null
        };
        plot.resize = function () {
            var width = placeholder.width(), height = placeholder.height();
            surface.resize(width, height);
            overlay.resize(width, height)
        };
        plot.hooks = hooks;
        initPlugins(plot);
        parseOptions(options_);
        setupCanvases();
        setData(data_);
        setupGrid();
        draw();
        bindEvents();
        function executeHooks(hook, args) {
            args = [plot].concat(args);
            for (var i = 0; i < hook.length; ++i)hook[i].apply(this, args)
        }

        function initPlugins() {
            var classes = {Canvas: Canvas};
            for (var i = 0; i < plugins.length; ++i) {
                var p = plugins[i];
                p.init(plot, classes);
                if (p.options)$.extend(true, options, p.options)
            }
        }

        function parseOptions(opts) {
            $.extend(true, options, opts);
            if (opts && opts.colors) {
                options.colors = opts.colors
            }
            if (options.xaxis.color == null)options.xaxis.color = $.color.parse(options.grid.color).scale("a", .22).toString();
            if (options.yaxis.color == null)options.yaxis.color = $.color.parse(options.grid.color).scale("a", .22).toString();
            if (options.xaxis.tickColor == null)options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
            if (options.yaxis.tickColor == null)options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
            if (options.grid.borderColor == null)options.grid.borderColor = options.grid.color;
            if (options.grid.tickColor == null)options.grid.tickColor = $.color.parse(options.grid.color).scale("a", .22).toString();
            var i, axisOptions, axisCount, fontSize = placeholder.css("font-size"), fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13, fontDefaults = {style: placeholder.css("font-style"), size: Math.round(.8 * fontSizeDefault), variant: placeholder.css("font-variant"), weight: placeholder.css("font-weight"), family: placeholder.css("font-family")};
            axisCount = options.xaxes.length || 1;
            for (i = 0; i < axisCount; ++i) {
                axisOptions = options.xaxes[i];
                if (axisOptions && !axisOptions.tickColor) {
                    axisOptions.tickColor = axisOptions.color
                }
                axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
                options.xaxes[i] = axisOptions;
                if (axisOptions.font) {
                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
                    if (!axisOptions.font.color) {
                        axisOptions.font.color = axisOptions.color
                    }
                    if (!axisOptions.font.lineHeight) {
                        axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15)
                    }
                }
            }
            axisCount = options.yaxes.length || 1;
            for (i = 0; i < axisCount; ++i) {
                axisOptions = options.yaxes[i];
                if (axisOptions && !axisOptions.tickColor) {
                    axisOptions.tickColor = axisOptions.color
                }
                axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
                options.yaxes[i] = axisOptions;
                if (axisOptions.font) {
                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
                    if (!axisOptions.font.color) {
                        axisOptions.font.color = axisOptions.color
                    }
                    if (!axisOptions.font.lineHeight) {
                        axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15)
                    }
                }
            }
            if (options.xaxis.noTicks && options.xaxis.ticks == null)options.xaxis.ticks = options.xaxis.noTicks;
            if (options.yaxis.noTicks && options.yaxis.ticks == null)options.yaxis.ticks = options.yaxis.noTicks;
            if (options.x2axis) {
                options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
                options.xaxes[1].position = "top"
            }
            if (options.y2axis) {
                options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
                options.yaxes[1].position = "right"
            }
            if (options.grid.coloredAreas)options.grid.markings = options.grid.coloredAreas;
            if (options.grid.coloredAreasColor)options.grid.markingsColor = options.grid.coloredAreasColor;
            if (options.lines)$.extend(true, options.series.lines, options.lines);
            if (options.points)$.extend(true, options.series.points, options.points);
            if (options.bars)$.extend(true, options.series.bars, options.bars);
            if (options.shadowSize != null)options.series.shadowSize = options.shadowSize;
            if (options.highlightColor != null)options.series.highlightColor = options.highlightColor;
            for (i = 0; i < options.xaxes.length; ++i)getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
            for (i = 0; i < options.yaxes.length; ++i)getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];
            for (var n in hooks)if (options.hooks[n] && options.hooks[n].length)hooks[n] = hooks[n].concat(options.hooks[n]);
            executeHooks(hooks.processOptions, [options])
        }

        function setData(d) {
            series = parseData(d);
            fillInSeriesOptions();
            processData()
        }

        function parseData(d) {
            var res = [];
            for (var i = 0; i < d.length; ++i) {
                var s = $.extend(true, {}, options.series);
                if (d[i].data != null) {
                    s.data = d[i].data;
                    delete d[i].data;
                    $.extend(true, s, d[i]);
                    d[i].data = s.data
                } else s.data = d[i];
                res.push(s)
            }
            return res
        }

        function axisNumber(obj, coord) {
            var a = obj[coord + "axis"];
            if (typeof a == "object")a = a.n;
            if (typeof a != "number")a = 1;
            return a
        }

        function allAxes() {
            return $.grep(xaxes.concat(yaxes), function (a) {
                return a
            })
        }

        function canvasToAxisCoords(pos) {
            var res = {}, i, axis;
            for (i = 0; i < xaxes.length; ++i) {
                axis = xaxes[i];
                if (axis && axis.used)res["x" + axis.n] = axis.c2p(pos.left)
            }
            for (i = 0; i < yaxes.length; ++i) {
                axis = yaxes[i];
                if (axis && axis.used)res["y" + axis.n] = axis.c2p(pos.top)
            }
            if (res.x1 !== undefined)res.x = res.x1;
            if (res.y1 !== undefined)res.y = res.y1;
            return res
        }

        function axisToCanvasCoords(pos) {
            var res = {}, i, axis, key;
            for (i = 0; i < xaxes.length; ++i) {
                axis = xaxes[i];
                if (axis && axis.used) {
                    key = "x" + axis.n;
                    if (pos[key] == null && axis.n == 1)key = "x";
                    if (pos[key] != null) {
                        res.left = axis.p2c(pos[key]);
                        break
                    }
                }
            }
            for (i = 0; i < yaxes.length; ++i) {
                axis = yaxes[i];
                if (axis && axis.used) {
                    key = "y" + axis.n;
                    if (pos[key] == null && axis.n == 1)key = "y";
                    if (pos[key] != null) {
                        res.top = axis.p2c(pos[key]);
                        break
                    }
                }
            }
            return res
        }

        function getOrCreateAxis(axes, number) {
            if (!axes[number - 1])axes[number - 1] = {n: number, direction: axes == xaxes ? "x" : "y", options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)};
            return axes[number - 1]
        }

        function fillInSeriesOptions() {
            var neededColors = series.length, maxIndex = -1, i;
            for (i = 0; i < series.length; ++i) {
                var sc = series[i].color;
                if (sc != null) {
                    neededColors--;
                    if (typeof sc == "number" && sc > maxIndex) {
                        maxIndex = sc
                    }
                }
            }
            if (neededColors <= maxIndex) {
                neededColors = maxIndex + 1
            }
            var c, colors = [], colorPool = options.colors, colorPoolSize = colorPool.length, variation = 0;
            for (i = 0; i < neededColors; i++) {
                c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
                if (i % colorPoolSize == 0 && i) {
                    if (variation >= 0) {
                        if (variation < .5) {
                            variation = -variation - .2
                        } else variation = 0
                    } else variation = -variation
                }
                colors[i] = c.scale("rgb", 1 + variation)
            }
            var colori = 0, s;
            for (i = 0; i < series.length; ++i) {
                s = series[i];
                if (s.color == null) {
                    s.color = colors[colori].toString();
                    ++colori
                } else if (typeof s.color == "number")s.color = colors[s.color].toString();
                if (s.lines.show == null) {
                    var v, show = true;
                    for (v in s)if (s[v] && s[v].show) {
                        show = false;
                        break
                    }
                    if (show)s.lines.show = true
                }
                if (s.lines.zero == null) {
                    s.lines.zero = !!s.lines.fill
                }
                s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
                s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"))
            }
        }

        function processData() {
            var topSentry = Number.POSITIVE_INFINITY, bottomSentry = Number.NEGATIVE_INFINITY, fakeInfinity = Number.MAX_VALUE, i, j, k, m, length, s, points, ps, x, y, axis, val, f, p, data, format;

            function updateAxis(axis, min, max) {
                if (min < axis.datamin && min != -fakeInfinity)axis.datamin = min;
                if (max > axis.datamax && max != fakeInfinity)axis.datamax = max
            }

            $.each(allAxes(), function (_, axis) {
                axis.datamin = topSentry;
                axis.datamax = bottomSentry;
                axis.used = false
            });
            for (i = 0; i < series.length; ++i) {
                s = series[i];
                s.datapoints = {points: []};
                executeHooks(hooks.processRawData, [s, s.data, s.datapoints])
            }
            for (i = 0; i < series.length; ++i) {
                s = series[i];
                data = s.data;
                format = s.datapoints.format;
                if (!format) {
                    format = [];
                    format.push({x: true, number: true, required: true});
                    format.push({y: true, number: true, required: true});
                    if (s.bars.show || s.lines.show && s.lines.fill) {
                        var autoscale = !!(s.bars.show && s.bars.zero || s.lines.show && s.lines.zero);
                        format.push({y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale});
                        if (s.bars.horizontal) {
                            delete format[format.length - 1].y;
                            format[format.length - 1].x = true
                        }
                    }
                    s.datapoints.format = format
                }
                if (s.datapoints.pointsize != null)continue;
                s.datapoints.pointsize = format.length;
                ps = s.datapoints.pointsize;
                points = s.datapoints.points;
                var insertSteps = s.lines.show && s.lines.steps;
                s.xaxis.used = s.yaxis.used = true;
                for (j = k = 0; j < data.length; ++j, k += ps) {
                    p = data[j];
                    var nullify = p == null;
                    if (!nullify) {
                        for (m = 0; m < ps; ++m) {
                            val = p[m];
                            f = format[m];
                            if (f) {
                                if (f.number && val != null) {
                                    val = +val;
                                    if (isNaN(val))val = null; else if (val == Infinity)val = fakeInfinity; else if (val == -Infinity)val = -fakeInfinity
                                }
                                if (val == null) {
                                    if (f.required)nullify = true;
                                    if (f.defaultValue != null)val = f.defaultValue
                                }
                            }
                            points[k + m] = val
                        }
                    }
                    if (nullify) {
                        for (m = 0; m < ps; ++m) {
                            val = points[k + m];
                            if (val != null) {
                                f = format[m];
                                if (f.autoscale !== false) {
                                    if (f.x) {
                                        updateAxis(s.xaxis, val, val)
                                    }
                                    if (f.y) {
                                        updateAxis(s.yaxis, val, val)
                                    }
                                }
                            }
                            points[k + m] = null
                        }
                    } else {
                        if (insertSteps && k > 0 && points[k - ps] != null && points[k - ps] != points[k] && points[k - ps + 1] != points[k + 1]) {
                            for (m = 0; m < ps; ++m)points[k + ps + m] = points[k + m];
                            points[k + 1] = points[k - ps + 1];
                            k += ps
                        }
                    }
                }
            }
            for (i = 0; i < series.length; ++i) {
                s = series[i];
                executeHooks(hooks.processDatapoints, [s, s.datapoints])
            }
            for (i = 0; i < series.length; ++i) {
                s = series[i];
                points = s.datapoints.points;
                ps = s.datapoints.pointsize;
                format = s.datapoints.format;
                var xmin = topSentry, ymin = topSentry, xmax = bottomSentry, ymax = bottomSentry;
                for (j = 0; j < points.length; j += ps) {
                    if (points[j] == null)continue;
                    for (m = 0; m < ps; ++m) {
                        val = points[j + m];
                        f = format[m];
                        if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)continue;
                        if (f.x) {
                            if (val < xmin)xmin = val;
                            if (val > xmax)xmax = val
                        }
                        if (f.y) {
                            if (val < ymin)ymin = val;
                            if (val > ymax)ymax = val
                        }
                    }
                }
                if (s.bars.show) {
                    var delta;
                    switch (s.bars.align) {
                        case"left":
                            delta = 0;
                            break;
                        case"right":
                            delta = -s.bars.barWidth;
                            break;
                        default:
                            delta = -s.bars.barWidth / 2
                    }
                    if (s.bars.horizontal) {
                        ymin += delta;
                        ymax += delta + s.bars.barWidth
                    } else {
                        xmin += delta;
                        xmax += delta + s.bars.barWidth
                    }
                }
                updateAxis(s.xaxis, xmin, xmax);
                updateAxis(s.yaxis, ymin, ymax)
            }
            $.each(allAxes(), function (_, axis) {
                if (axis.datamin == topSentry)axis.datamin = null;
                if (axis.datamax == bottomSentry)axis.datamax = null
            })
        }

        function setupCanvases() {
            placeholder.css("padding", 0).children().filter(function () {
                return!$(this).hasClass("flot-overlay") && !$(this).hasClass("flot-base")
            }).remove();
            if (placeholder.css("position") == "static")placeholder.css("position", "relative");
            surface = new Canvas("flot-base", placeholder);
            overlay = new Canvas("flot-overlay", placeholder);
            ctx = surface.context;
            octx = overlay.context;
            eventHolder = $(overlay.element).unbind();
            var existing = placeholder.data("plot");
            if (existing) {
                existing.shutdown();
                overlay.clear()
            }
            placeholder.data("plot", plot)
        }

        function bindEvents() {
            if (options.grid.hoverable) {
                eventHolder.mousemove(onMouseMove);
                eventHolder.bind("mouseleave", onMouseLeave)
            }
            if (options.grid.clickable)eventHolder.click(onClick);
            executeHooks(hooks.bindEvents, [eventHolder])
        }

        function shutdown() {
            if (redrawTimeout)clearTimeout(redrawTimeout);
            eventHolder.unbind("mousemove", onMouseMove);
            eventHolder.unbind("mouseleave", onMouseLeave);
            eventHolder.unbind("click", onClick);
            executeHooks(hooks.shutdown, [eventHolder])
        }

        function setTransformationHelpers(axis) {
            function identity(x) {
                return x
            }

            var s, m, t = axis.options.transform || identity, it = axis.options.inverseTransform;
            if (axis.direction == "x") {
                s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
                m = Math.min(t(axis.max), t(axis.min))
            } else {
                s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
                s = -s;
                m = Math.max(t(axis.max), t(axis.min))
            }
            if (t == identity)axis.p2c = function (p) {
                return(p - m) * s
            }; else axis.p2c = function (p) {
                return(t(p) - m) * s
            };
            if (!it)axis.c2p = function (c) {
                return m + c / s
            }; else axis.c2p = function (c) {
                return it(m + c / s)
            }
        }

        function measureTickLabels(axis) {
            var opts = axis.options, ticks = axis.ticks || [], labelWidth = opts.labelWidth || 0, labelHeight = opts.labelHeight || 0, maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null), legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, font = opts.font || "flot-tick-label tickLabel";
            for (var i = 0; i < ticks.length; ++i) {
                var t = ticks[i];
                if (!t.label)continue;
                var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
                labelWidth = Math.max(labelWidth, info.width);
                labelHeight = Math.max(labelHeight, info.height)
            }
            axis.labelWidth = opts.labelWidth || labelWidth;
            axis.labelHeight = opts.labelHeight || labelHeight
        }

        function allocateAxisBoxFirstPhase(axis) {
            var lw = axis.labelWidth, lh = axis.labelHeight, pos = axis.options.position, isXAxis = axis.direction === "x", tickLength = axis.options.tickLength, axisMargin = options.grid.axisMargin, padding = options.grid.labelMargin, innermost = true, outermost = true, first = true, found = false;
            $.each(isXAxis ? xaxes : yaxes, function (i, a) {
                if (a && a.reserveSpace) {
                    if (a === axis) {
                        found = true
                    } else if (a.options.position === pos) {
                        if (found) {
                            outermost = false
                        } else {
                            innermost = false
                        }
                    }
                    if (!found) {
                        first = false
                    }
                }
            });
            if (outermost) {
                axisMargin = 0
            }
            if (tickLength == null) {
                tickLength = first ? "full" : 5
            }
            if (!isNaN(+tickLength))padding += +tickLength;
            if (isXAxis) {
                lh += padding;
                if (pos == "bottom") {
                    plotOffset.bottom += lh + axisMargin;
                    axis.box = {top: surface.height - plotOffset.bottom, height: lh}
                } else {
                    axis.box = {top: plotOffset.top + axisMargin, height: lh};
                    plotOffset.top += lh + axisMargin
                }
            } else {
                lw += padding;
                if (pos == "left") {
                    axis.box = {left: plotOffset.left + axisMargin, width: lw};
                    plotOffset.left += lw + axisMargin
                } else {
                    plotOffset.right += lw + axisMargin;
                    axis.box = {left: surface.width - plotOffset.right, width: lw}
                }
            }
            axis.position = pos;
            axis.tickLength = tickLength;
            axis.box.padding = padding;
            axis.innermost = innermost
        }

        function allocateAxisBoxSecondPhase(axis) {
            if (axis.direction == "x") {
                axis.box.left = plotOffset.left - axis.labelWidth / 2;
                axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth
            } else {
                axis.box.top = plotOffset.top - axis.labelHeight / 2;
                axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight
            }
        }

        function adjustLayoutForThingsStickingOut() {
            var minMargin = options.grid.minBorderMargin, axis, i;
            if (minMargin == null) {
                minMargin = 0;
                for (i = 0; i < series.length; ++i)minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth / 2))
            }
            var margins = {left: minMargin, right: minMargin, top: minMargin, bottom: minMargin};
            $.each(allAxes(), function (_, axis) {
                if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
                    var lastTick = axis.ticks[axis.ticks.length - 1];
                    if (axis.direction === "x") {
                        margins.left = Math.max(margins.left, axis.labelWidth / 2);
                        if (lastTick.v <= axis.max) {
                            margins.right = Math.max(margins.right, axis.labelWidth / 2)
                        }
                    } else {
                        margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
                        if (lastTick.v <= axis.max) {
                            margins.top = Math.max(margins.top, axis.labelHeight / 2)
                        }
                    }
                }
            });
            plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
            plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
            plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
            plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom))
        }

        function setupGrid() {
            var i, axes = allAxes(), showGrid = options.grid.show;
            for (var a in plotOffset) {
                var margin = options.grid.margin || 0;
                plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0
            }
            executeHooks(hooks.processOffset, [plotOffset]);
            for (var a in plotOffset) {
                if (typeof options.grid.borderWidth == "object") {
                    plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0
                } else {
                    plotOffset[a] += showGrid ? options.grid.borderWidth : 0
                }
            }
            $.each(axes, function (_, axis) {
                axis.show = axis.options.show;
                if (axis.show == null)axis.show = axis.used;
                axis.reserveSpace = axis.show || axis.options.reserveSpace;
                setRange(axis)
            });
            if (showGrid) {
                var allocatedAxes = $.grep(axes, function (axis) {
                    return axis.reserveSpace
                });
                $.each(allocatedAxes, function (_, axis) {
                    setupTickGeneration(axis);
                    setTicks(axis);
                    snapRangeToTicks(axis, axis.ticks);
                    measureTickLabels(axis)
                });
                for (i = allocatedAxes.length - 1; i >= 0; --i)allocateAxisBoxFirstPhase(allocatedAxes[i]);
                adjustLayoutForThingsStickingOut();
                $.each(allocatedAxes, function (_, axis) {
                    allocateAxisBoxSecondPhase(axis)
                })
            }
            plotWidth = surface.width - plotOffset.left - plotOffset.right;
            plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
            $.each(axes, function (_, axis) {
                setTransformationHelpers(axis)
            });
            if (showGrid) {
                drawAxisLabels()
            }
            insertLegend()
        }

        function setRange(axis) {
            var opts = axis.options, min = +(opts.min != null ? opts.min : axis.datamin), max = +(opts.max != null ? opts.max : axis.datamax), delta = max - min;
            if (delta == 0) {
                var widen = max == 0 ? 1 : .01;
                if (opts.min == null)min -= widen;
                if (opts.max == null || opts.min != null)max += widen
            } else {
                var margin = opts.autoscaleMargin;
                if (margin != null) {
                    if (opts.min == null) {
                        min -= delta * margin;
                        if (min < 0 && axis.datamin != null && axis.datamin >= 0)min = 0
                    }
                    if (opts.max == null) {
                        max += delta * margin;
                        if (max > 0 && axis.datamax != null && axis.datamax <= 0)max = 0
                    }
                }
            }
            axis.min = min;
            axis.max = max
        }

        function setupTickGeneration(axis) {
            var opts = axis.options;
            var noTicks;
            if (typeof opts.ticks == "number" && opts.ticks > 0)noTicks = opts.ticks; else noTicks = .3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);
            var delta = (axis.max - axis.min) / noTicks, dec = -Math.floor(Math.log(delta) / Math.LN10), maxDec = opts.tickDecimals;
            if (maxDec != null && dec > maxDec) {
                dec = maxDec
            }
            var magn = Math.pow(10, -dec), norm = delta / magn, size;
            if (norm < 1.5) {
                size = 1
            } else if (norm < 3) {
                size = 2;
                if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
                    size = 2.5;
                    ++dec
                }
            } else if (norm < 7.5) {
                size = 5
            } else {
                size = 10
            }
            size *= magn;
            if (opts.minTickSize != null && size < opts.minTickSize) {
                size = opts.minTickSize
            }
            axis.delta = delta;
            axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
            axis.tickSize = opts.tickSize || size;
            if (opts.mode == "time" && !axis.tickGenerator) {
                throw new Error("Time mode requires the flot.time plugin.")
            }
            if (!axis.tickGenerator) {
                axis.tickGenerator = function (axis) {
                    var ticks = [], start = floorInBase(axis.min, axis.tickSize), i = 0, v = Number.NaN, prev;
                    do {
                        prev = v;
                        v = start + i * axis.tickSize;
                        ticks.push(v);
                        ++i
                    } while (v < axis.max && v != prev);
                    return ticks
                };
                axis.tickFormatter = function (value, axis) {
                    var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
                    var formatted = "" + Math.round(value * factor) / factor;
                    if (axis.tickDecimals != null) {
                        var decimal = formatted.indexOf(".");
                        var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
                        if (precision < axis.tickDecimals) {
                            return(precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision)
                        }
                    }
                    return formatted
                }
            }
            if ($.isFunction(opts.tickFormatter))axis.tickFormatter = function (v, axis) {
                return"" + opts.tickFormatter(v, axis)
            };
            if (opts.alignTicksWithAxis != null) {
                var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
                if (otherAxis && otherAxis.used && otherAxis != axis) {
                    var niceTicks = axis.tickGenerator(axis);
                    if (niceTicks.length > 0) {
                        if (opts.min == null)axis.min = Math.min(axis.min, niceTicks[0]);
                        if (opts.max == null && niceTicks.length > 1)axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1])
                    }
                    axis.tickGenerator = function (axis) {
                        var ticks = [], v, i;
                        for (i = 0; i < otherAxis.ticks.length; ++i) {
                            v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
                            v = axis.min + v * (axis.max - axis.min);
                            ticks.push(v)
                        }
                        return ticks
                    };
                    if (!axis.mode && opts.tickDecimals == null) {
                        var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1), ts = axis.tickGenerator(axis);
                        if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))axis.tickDecimals = extraDec
                    }
                }
            }
        }

        function setTicks(axis) {
            var oticks = axis.options.ticks, ticks = [];
            if (oticks == null || typeof oticks == "number" && oticks > 0)ticks = axis.tickGenerator(axis); else if (oticks) {
                if ($.isFunction(oticks))ticks = oticks(axis); else ticks = oticks
            }
            var i, v;
            axis.ticks = [];
            for (i = 0; i < ticks.length; ++i) {
                var label = null;
                var t = ticks[i];
                if (typeof t == "object") {
                    v = +t[0];
                    if (t.length > 1)label = t[1]
                } else v = +t;
                if (label == null)label = axis.tickFormatter(v, axis);
                if (!isNaN(v))axis.ticks.push({v: v, label: label})
            }
        }

        function snapRangeToTicks(axis, ticks) {
            if (axis.options.autoscaleMargin && ticks.length > 0) {
                if (axis.options.min == null)axis.min = Math.min(axis.min, ticks[0].v);
                if (axis.options.max == null && ticks.length > 1)axis.max = Math.max(axis.max, ticks[ticks.length - 1].v)
            }
        }

        function draw() {
            surface.clear();
            executeHooks(hooks.drawBackground, [ctx]);
            var grid = options.grid;
            if (grid.show && grid.backgroundColor)drawBackground();
            if (grid.show && !grid.aboveData) {
                drawGrid()
            }
            for (var i = 0; i < series.length; ++i) {
                executeHooks(hooks.drawSeries, [ctx, series[i]]);
                drawSeries(series[i])
            }
            executeHooks(hooks.draw, [ctx]);
            if (grid.show && grid.aboveData) {
                drawGrid()
            }
            surface.render();
            triggerRedrawOverlay()
        }

        function extractRange(ranges, coord) {
            var axis, from, to, key, axes = allAxes();
            for (var i = 0; i < axes.length; ++i) {
                axis = axes[i];
                if (axis.direction == coord) {
                    key = coord + axis.n + "axis";
                    if (!ranges[key] && axis.n == 1)key = coord + "axis";
                    if (ranges[key]) {
                        from = ranges[key].from;
                        to = ranges[key].to;
                        break
                    }
                }
            }
            if (!ranges[key]) {
                axis = coord == "x" ? xaxes[0] : yaxes[0];
                from = ranges[coord + "1"];
                to = ranges[coord + "2"]
            }
            if (from != null && to != null && from > to) {
                var tmp = from;
                from = to;
                to = tmp
            }
            return{from: from, to: to, axis: axis}
        }

        function drawBackground() {
            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);
            ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
            ctx.fillRect(0, 0, plotWidth, plotHeight);
            ctx.restore()
        }

        function drawGrid() {
            var i, axes, bw, bc;
            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);
            var markings = options.grid.markings;
            if (markings) {
                if ($.isFunction(markings)) {
                    axes = plot.getAxes();
                    axes.xmin = axes.xaxis.min;
                    axes.xmax = axes.xaxis.max;
                    axes.ymin = axes.yaxis.min;
                    axes.ymax = axes.yaxis.max;
                    markings = markings(axes)
                }
                for (i = 0; i < markings.length; ++i) {
                    var m = markings[i], xrange = extractRange(m, "x"), yrange = extractRange(m, "y");
                    if (xrange.from == null)xrange.from = xrange.axis.min;
                    if (xrange.to == null)xrange.to = xrange.axis.max;
                    if (yrange.from == null)yrange.from = yrange.axis.min;
                    if (yrange.to == null)yrange.to = yrange.axis.max;
                    if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max || yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)continue;
                    xrange.from = Math.max(xrange.from, xrange.axis.min);
                    xrange.to = Math.min(xrange.to, xrange.axis.max);
                    yrange.from = Math.max(yrange.from, yrange.axis.min);
                    yrange.to = Math.min(yrange.to, yrange.axis.max);
                    if (xrange.from == xrange.to && yrange.from == yrange.to)continue;
                    xrange.from = xrange.axis.p2c(xrange.from);
                    xrange.to = xrange.axis.p2c(xrange.to);
                    yrange.from = yrange.axis.p2c(yrange.from);
                    yrange.to = yrange.axis.p2c(yrange.to);
                    if (xrange.from == xrange.to || yrange.from == yrange.to) {
                        ctx.beginPath();
                        ctx.strokeStyle = m.color || options.grid.markingsColor;
                        ctx.lineWidth = m.lineWidth || options.grid.markingsLineWidth;
                        ctx.moveTo(xrange.from, yrange.from);
                        ctx.lineTo(xrange.to, yrange.to);
                        ctx.stroke()
                    } else {
                        ctx.fillStyle = m.color || options.grid.markingsColor;
                        ctx.fillRect(xrange.from, yrange.to, xrange.to - xrange.from, yrange.from - yrange.to)
                    }
                }
            }
            axes = allAxes();
            bw = options.grid.borderWidth;
            for (var j = 0; j < axes.length; ++j) {
                var axis = axes[j], box = axis.box, t = axis.tickLength, x, y, xoff, yoff;
                if (!axis.show || axis.ticks.length == 0)continue;
                ctx.lineWidth = 1;
                if (axis.direction == "x") {
                    x = 0;
                    if (t == "full")y = axis.position == "top" ? 0 : plotHeight; else y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0)
                } else {
                    y = 0;
                    if (t == "full")x = axis.position == "left" ? 0 : plotWidth; else x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0)
                }
                if (!axis.innermost) {
                    ctx.strokeStyle = axis.options.color;
                    ctx.beginPath();
                    xoff = yoff = 0;
                    if (axis.direction == "x")xoff = plotWidth + 1; else yoff = plotHeight + 1;
                    if (ctx.lineWidth == 1) {
                        if (axis.direction == "x") {
                            y = Math.floor(y) + .5
                        } else {
                            x = Math.floor(x) + .5
                        }
                    }
                    ctx.moveTo(x, y);
                    ctx.lineTo(x + xoff, y + yoff);
                    ctx.stroke()
                }
                ctx.strokeStyle = axis.options.tickColor;
                ctx.beginPath();
                for (i = 0; i < axis.ticks.length; ++i) {
                    var v = axis.ticks[i].v;
                    xoff = yoff = 0;
                    if (isNaN(v) || v < axis.min || v > axis.max || t == "full" && (typeof bw == "object" && bw[axis.position] > 0 || bw > 0) && (v == axis.min || v == axis.max))continue;
                    if (axis.direction == "x") {
                        x = axis.p2c(v);
                        yoff = t == "full" ? -plotHeight : t;
                        if (axis.position == "top")yoff = -yoff
                    } else {
                        y = axis.p2c(v);
                        xoff = t == "full" ? -plotWidth : t;
                        if (axis.position == "left")xoff = -xoff
                    }
                    if (ctx.lineWidth == 1) {
                        if (axis.direction == "x")x = Math.floor(x) + .5; else y = Math.floor(y) + .5
                    }
                    ctx.moveTo(x, y);
                    ctx.lineTo(x + xoff, y + yoff)
                }
                ctx.stroke()
            }
            if (bw) {
                bc = options.grid.borderColor;
                if (typeof bw == "object" || typeof bc == "object") {
                    if (typeof bw !== "object") {
                        bw = {top: bw, right: bw, bottom: bw, left: bw}
                    }
                    if (typeof bc !== "object") {
                        bc = {top: bc, right: bc, bottom: bc, left: bc}
                    }
                    if (bw.top > 0) {
                        ctx.strokeStyle = bc.top;
                        ctx.lineWidth = bw.top;
                        ctx.beginPath();
                        ctx.moveTo(0 - bw.left, 0 - bw.top / 2);
                        ctx.lineTo(plotWidth, 0 - bw.top / 2);
                        ctx.stroke()
                    }
                    if (bw.right > 0) {
                        ctx.strokeStyle = bc.right;
                        ctx.lineWidth = bw.right;
                        ctx.beginPath();
                        ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
                        ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
                        ctx.stroke()
                    }
                    if (bw.bottom > 0) {
                        ctx.strokeStyle = bc.bottom;
                        ctx.lineWidth = bw.bottom;
                        ctx.beginPath();
                        ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
                        ctx.lineTo(0, plotHeight + bw.bottom / 2);
                        ctx.stroke()
                    }
                    if (bw.left > 0) {
                        ctx.strokeStyle = bc.left;
                        ctx.lineWidth = bw.left;
                        ctx.beginPath();
                        ctx.moveTo(0 - bw.left / 2, plotHeight + bw.bottom);
                        ctx.lineTo(0 - bw.left / 2, 0);
                        ctx.stroke()
                    }
                } else {
                    ctx.lineWidth = bw;
                    ctx.strokeStyle = options.grid.borderColor;
                    ctx.strokeRect(-bw / 2, -bw / 2, plotWidth + bw, plotHeight + bw)
                }
            }
            ctx.restore()
        }

        function drawAxisLabels() {
            $.each(allAxes(), function (_, axis) {
                var box = axis.box, legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis", layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles, font = axis.options.font || "flot-tick-label tickLabel", tick, x, y, halign, valign;
                surface.removeText(layer);
                if (!axis.show || axis.ticks.length == 0)return;
                for (var i = 0; i < axis.ticks.length; ++i) {
                    tick = axis.ticks[i];
                    if (!tick.label || tick.v < axis.min || tick.v > axis.max)continue;
                    if (axis.direction == "x") {
                        halign = "center";
                        x = plotOffset.left + axis.p2c(tick.v);
                        if (axis.position == "bottom") {
                            y = box.top + box.padding
                        } else {
                            y = box.top + box.height - box.padding;
                            valign = "bottom"
                        }
                    } else {
                        valign = "middle";
                        y = plotOffset.top + axis.p2c(tick.v);
                        if (axis.position == "left") {
                            x = box.left + box.width - box.padding;
                            halign = "right"
                        } else {
                            x = box.left + box.padding
                        }
                    }
                    surface.addText(layer, x, y, tick.label, font, null, null, halign, valign)
                }
            })
        }

        function drawSeries(series) {
            if (series.lines.show)drawSeriesLines(series);
            if (series.bars.show)drawSeriesBars(series);
            if (series.points.show)drawSeriesPoints(series)
        }

        function drawSeriesLines(series) {
            function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
                var points = datapoints.points, ps = datapoints.pointsize, prevx = null, prevy = null;
                ctx.beginPath();
                for (var i = ps; i < points.length; i += ps) {
                    var x1 = points[i - ps], y1 = points[i - ps + 1], x2 = points[i], y2 = points[i + 1];
                    if (x1 == null || x2 == null)continue;
                    if (y1 <= y2 && y1 < axisy.min) {
                        if (y2 < axisy.min)continue;
                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y1 = axisy.min
                    } else if (y2 <= y1 && y2 < axisy.min) {
                        if (y1 < axisy.min)continue;
                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y2 = axisy.min
                    }
                    if (y1 >= y2 && y1 > axisy.max) {
                        if (y2 > axisy.max)continue;
                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y1 = axisy.max
                    } else if (y2 >= y1 && y2 > axisy.max) {
                        if (y1 > axisy.max)continue;
                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y2 = axisy.max
                    }
                    if (x1 <= x2 && x1 < axisx.min) {
                        if (x2 < axisx.min)continue;
                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x1 = axisx.min
                    } else if (x2 <= x1 && x2 < axisx.min) {
                        if (x1 < axisx.min)continue;
                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x2 = axisx.min
                    }
                    if (x1 >= x2 && x1 > axisx.max) {
                        if (x2 > axisx.max)continue;
                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x1 = axisx.max
                    } else if (x2 >= x1 && x2 > axisx.max) {
                        if (x1 > axisx.max)continue;
                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x2 = axisx.max
                    }
                    if (x1 != prevx || y1 != prevy)ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);
                    prevx = x2;
                    prevy = y2;
                    ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset)
                }
                ctx.stroke()
            }

            function plotLineArea(datapoints, axisx, axisy) {
                var points = datapoints.points, ps = datapoints.pointsize, bottom = Math.min(Math.max(0, axisy.min), axisy.max), i = 0, top, areaOpen = false, ypos = 1, segmentStart = 0, segmentEnd = 0;
                while (true) {
                    if (ps > 0 && i > points.length + ps)break;
                    i += ps;
                    var x1 = points[i - ps], y1 = points[i - ps + ypos], x2 = points[i], y2 = points[i + ypos];
                    if (areaOpen) {
                        if (ps > 0 && x1 != null && x2 == null) {
                            segmentEnd = i;
                            ps = -ps;
                            ypos = 2;
                            continue
                        }
                        if (ps < 0 && i == segmentStart + ps) {
                            ctx.fill();
                            areaOpen = false;
                            ps = -ps;
                            ypos = 1;
                            i = segmentStart = segmentEnd + ps;
                            continue
                        }
                    }
                    if (x1 == null || x2 == null)continue;
                    if (x1 <= x2 && x1 < axisx.min) {
                        if (x2 < axisx.min)continue;
                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x1 = axisx.min
                    } else if (x2 <= x1 && x2 < axisx.min) {
                        if (x1 < axisx.min)continue;
                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x2 = axisx.min
                    }
                    if (x1 >= x2 && x1 > axisx.max) {
                        if (x2 > axisx.max)continue;
                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x1 = axisx.max
                    } else if (x2 >= x1 && x2 > axisx.max) {
                        if (x1 > axisx.max)continue;
                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x2 = axisx.max
                    }
                    if (!areaOpen) {
                        ctx.beginPath();
                        ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
                        areaOpen = true
                    }
                    if (y1 >= axisy.max && y2 >= axisy.max) {
                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
                        continue
                    } else if (y1 <= axisy.min && y2 <= axisy.min) {
                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
                        continue
                    }
                    var x1old = x1, x2old = x2;
                    if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y1 = axisy.min
                    } else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y2 = axisy.min
                    }
                    if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y1 = axisy.max
                    } else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y2 = axisy.max
                    }
                    if (x1 != x1old) {
                        ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1))
                    }
                    ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
                    if (x2 != x2old) {
                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
                        ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2))
                    }
                }
            }

            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);
            ctx.lineJoin = "round";
            var lw = series.lines.lineWidth, sw = series.shadowSize;
            if (lw > 0 && sw > 0) {
                ctx.lineWidth = sw;
                ctx.strokeStyle = "rgba(0,0,0,0.1)";
                var angle = Math.PI / 18;
                plotLine(series.datapoints, Math.sin(angle) * (lw / 2 + sw / 2), Math.cos(angle) * (lw / 2 + sw / 2), series.xaxis, series.yaxis);
                ctx.lineWidth = sw / 2;
                plotLine(series.datapoints, Math.sin(angle) * (lw / 2 + sw / 4), Math.cos(angle) * (lw / 2 + sw / 4), series.xaxis, series.yaxis)
            }
            ctx.lineWidth = lw;
            ctx.strokeStyle = series.color;
            var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
            if (fillStyle) {
                ctx.fillStyle = fillStyle;
                plotLineArea(series.datapoints, series.xaxis, series.yaxis)
            }
            if (lw > 0)plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
            ctx.restore()
        }

        function drawSeriesPoints(series) {
            function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
                var points = datapoints.points, ps = datapoints.pointsize;
                for (var i = 0; i < points.length; i += ps) {
                    var x = points[i], y = points[i + 1];
                    if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)continue;
                    ctx.beginPath();
                    x = axisx.p2c(x);
                    y = axisy.p2c(y) + offset;
                    if (symbol == "circle")ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false); else symbol(ctx, x, y, radius, shadow);
                    ctx.closePath();
                    if (fillStyle) {
                        ctx.fillStyle = fillStyle;
                        ctx.fill()
                    }
                    ctx.stroke()
                }
            }

            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);
            var lw = series.points.lineWidth, sw = series.shadowSize, radius = series.points.radius, symbol = series.points.symbol;
            if (lw == 0)lw = 1e-4;
            if (lw > 0 && sw > 0) {
                var w = sw / 2;
                ctx.lineWidth = w;
                ctx.strokeStyle = "rgba(0,0,0,0.1)";
                plotPoints(series.datapoints, radius, null, w + w / 2, true, series.xaxis, series.yaxis, symbol);
                ctx.strokeStyle = "rgba(0,0,0,0.2)";
                plotPoints(series.datapoints, radius, null, w / 2, true, series.xaxis, series.yaxis, symbol)
            }
            ctx.lineWidth = lw;
            ctx.strokeStyle = series.color;
            plotPoints(series.datapoints, radius, getFillStyle(series.points, series.color), 0, false, series.xaxis, series.yaxis, symbol);
            ctx.restore()
        }

        function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
            var left, right, bottom, top, drawLeft, drawRight, drawTop, drawBottom, tmp;
            if (horizontal) {
                drawBottom = drawRight = drawTop = true;
                drawLeft = false;
                left = b;
                right = x;
                top = y + barLeft;
                bottom = y + barRight;
                if (right < left) {
                    tmp = right;
                    right = left;
                    left = tmp;
                    drawLeft = true;
                    drawRight = false
                }
            } else {
                drawLeft = drawRight = drawTop = true;
                drawBottom = false;
                left = x + barLeft;
                right = x + barRight;
                bottom = b;
                top = y;
                if (top < bottom) {
                    tmp = top;
                    top = bottom;
                    bottom = tmp;
                    drawBottom = true;
                    drawTop = false
                }
            }
            if (right < axisx.min || left > axisx.max || top < axisy.min || bottom > axisy.max)return;
            if (left < axisx.min) {
                left = axisx.min;
                drawLeft = false
            }
            if (right > axisx.max) {
                right = axisx.max;
                drawRight = false
            }
            if (bottom < axisy.min) {
                bottom = axisy.min;
                drawBottom = false
            }
            if (top > axisy.max) {
                top = axisy.max;
                drawTop = false
            }
            left = axisx.p2c(left);
            bottom = axisy.p2c(bottom);
            right = axisx.p2c(right);
            top = axisy.p2c(top);
            if (fillStyleCallback) {
                c.fillStyle = fillStyleCallback(bottom, top);
                c.fillRect(left, top, right - left, bottom - top)
            }
            if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
                c.beginPath();
                c.moveTo(left, bottom);
                if (drawLeft)c.lineTo(left, top); else c.moveTo(left, top);
                if (drawTop)c.lineTo(right, top); else c.moveTo(right, top);
                if (drawRight)c.lineTo(right, bottom); else c.moveTo(right, bottom);
                if (drawBottom)c.lineTo(left, bottom); else c.moveTo(left, bottom);
                c.stroke()
            }
        }

        function drawSeriesBars(series) {
            function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
                var points = datapoints.points, ps = datapoints.pointsize;
                for (var i = 0; i < points.length; i += ps) {
                    if (points[i] == null)continue;
                    drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth)
                }
            }

            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);
            ctx.lineWidth = series.bars.lineWidth;
            ctx.strokeStyle = series.color;
            var barLeft;
            switch (series.bars.align) {
                case"left":
                    barLeft = 0;
                    break;
                case"right":
                    barLeft = -series.bars.barWidth;
                    break;
                default:
                    barLeft = -series.bars.barWidth / 2
            }
            var fillStyleCallback = series.bars.fill ? function (bottom, top) {
                return getFillStyle(series.bars, series.color, bottom, top)
            } : null;
            plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
            ctx.restore()
        }

        function getFillStyle(filloptions, seriesColor, bottom, top) {
            var fill = filloptions.fill;
            if (!fill)return null;
            if (filloptions.fillColor)return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);
            var c = $.color.parse(seriesColor);
            c.a = typeof fill == "number" ? fill : .4;
            c.normalize();
            return c.toString()
        }

        function insertLegend() {
            if (options.legend.container != null) {
                $(options.legend.container).html("")
            } else {
                placeholder.find(".legend").remove()
            }
            if (!options.legend.show) {
                return
            }
            var fragments = [], entries = [], rowStarted = false, lf = options.legend.labelFormatter, s, label;
            for (var i = 0; i < series.length; ++i) {
                s = series[i];
                if (s.label) {
                    label = lf ? lf(s.label, s) : s.label;
                    if (label) {
                        entries.push({label: label, color: s.color})
                    }
                }
            }
            if (options.legend.sorted) {
                if ($.isFunction(options.legend.sorted)) {
                    entries.sort(options.legend.sorted)
                } else if (options.legend.sorted == "reverse") {
                    entries.reverse()
                } else {
                    var ascending = options.legend.sorted != "descending";
                    entries.sort(function (a, b) {
                        return a.label == b.label ? 0 : a.label < b.label != ascending ? 1 : -1
                    })
                }
            }
            for (var i = 0; i < entries.length; ++i) {
                var entry = entries[i];
                if (i % options.legend.noColumns == 0) {
                    if (rowStarted)fragments.push("</tr>");
                    fragments.push("<tr>");
                    rowStarted = true
                }
                fragments.push('<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' + '<td class="legendLabel">' + entry.label + "</td>")
            }
            if (rowStarted)fragments.push("</tr>");
            if (fragments.length == 0)return;
            var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + "</table>";
            if (options.legend.container != null)$(options.legend.container).html(table); else {
                var pos = "", p = options.legend.position, m = options.legend.margin;
                if (m[0] == null)m = [m, m];
                if (p.charAt(0) == "n")pos += "top:" + (m[1] + plotOffset.top) + "px;"; else if (p.charAt(0) == "s")pos += "bottom:" + (m[1] + plotOffset.bottom) + "px;";
                if (p.charAt(1) == "e")pos += "right:" + (m[0] + plotOffset.right) + "px;"; else if (p.charAt(1) == "w")pos += "left:" + (m[0] + plotOffset.left) + "px;";
                var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos + ";") + "</div>").appendTo(placeholder);
                if (options.legend.backgroundOpacity != 0) {
                    var c = options.legend.backgroundColor;
                    if (c == null) {
                        c = options.grid.backgroundColor;
                        if (c && typeof c == "string")c = $.color.parse(c); else c = $.color.extract(legend, "background-color");
                        c.a = 1;
                        c = c.toString()
                    }
                    var div = legend.children();
                    $('<div style="position:absolute;width:' + div.width() + "px;height:" + div.height() + "px;" + pos + "background-color:" + c + ';"> </div>').prependTo(legend).css("opacity", options.legend.backgroundOpacity)
                }
            }
        }

        var highlights = [], redrawTimeout = null;

        function findNearbyItem(mouseX, mouseY, seriesFilter) {
            var maxDistance = options.grid.mouseActiveRadius, smallestDistance = maxDistance * maxDistance + 1, item = null, foundPoint = false, i, j, ps;
            for (i = series.length - 1; i >= 0; --i) {
                if (!seriesFilter(series[i]))continue;
                var s = series[i], axisx = s.xaxis, axisy = s.yaxis, points = s.datapoints.points, mx = axisx.c2p(mouseX), my = axisy.c2p(mouseY), maxx = maxDistance / axisx.scale, maxy = maxDistance / axisy.scale;
                ps = s.datapoints.pointsize;
                if (axisx.options.inverseTransform)maxx = Number.MAX_VALUE;
                if (axisy.options.inverseTransform)maxy = Number.MAX_VALUE;
                if (s.lines.show || s.points.show) {
                    for (j = 0; j < points.length; j += ps) {
                        var x = points[j], y = points[j + 1];
                        if (x == null)continue;
                        if (x - mx > maxx || x - mx < -maxx || y - my > maxy || y - my < -maxy)continue;
                        var dx = Math.abs(axisx.p2c(x) - mouseX), dy = Math.abs(axisy.p2c(y) - mouseY), dist = dx * dx + dy * dy;
                        if (dist < smallestDistance) {
                            smallestDistance = dist;
                            item = [i, j / ps]
                        }
                    }
                }
                if (s.bars.show && !item) {
                    var barLeft, barRight;
                    switch (s.bars.align) {
                        case"left":
                            barLeft = 0;
                            break;
                        case"right":
                            barLeft = -s.bars.barWidth;
                            break;
                        default:
                            barLeft = -s.bars.barWidth / 2
                    }
                    barRight = barLeft + s.bars.barWidth;
                    for (j = 0; j < points.length; j += ps) {
                        var x = points[j], y = points[j + 1], b = points[j + 2];
                        if (x == null)continue;
                        if (series[i].bars.horizontal ? mx <= Math.max(b, x) && mx >= Math.min(b, x) && my >= y + barLeft && my <= y + barRight : mx >= x + barLeft && mx <= x + barRight && my >= Math.min(b, y) && my <= Math.max(b, y))item = [i, j / ps]
                    }
                }
            }
            if (item) {
                i = item[0];
                j = item[1];
                ps = series[i].datapoints.pointsize;
                return{datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps), dataIndex: j, series: series[i], seriesIndex: i}
            }
            return null
        }

        function onMouseMove(e) {
            if (options.grid.hoverable)triggerClickHoverEvent("plothover", e, function (s) {
                return s["hoverable"] != false
            })
        }

        function onMouseLeave(e) {
            if (options.grid.hoverable)triggerClickHoverEvent("plothover", e, function (s) {
                return false
            })
        }

        function onClick(e) {
            triggerClickHoverEvent("plotclick", e, function (s) {
                return s["clickable"] != false
            })
        }

        function triggerClickHoverEvent(eventname, event, seriesFilter) {
            var offset = eventHolder.offset(), canvasX = event.pageX - offset.left - plotOffset.left, canvasY = event.pageY - offset.top - plotOffset.top, pos = canvasToAxisCoords({left: canvasX, top: canvasY});
            pos.pageX = event.pageX;
            pos.pageY = event.pageY;
            var item = findNearbyItem(canvasX, canvasY, seriesFilter);
            if (item) {
                item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
                item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10)
            }
            if (options.grid.autoHighlight) {
                for (var i = 0; i < highlights.length; ++i) {
                    var h = highlights[i];
                    if (h.auto == eventname && !(item && h.series == item.series && h.point[0] == item.datapoint[0] && h.point[1] == item.datapoint[1]))unhighlight(h.series, h.point)
                }
                if (item)highlight(item.series, item.datapoint, eventname)
            }
            placeholder.trigger(eventname, [pos, item])
        }

        function triggerRedrawOverlay() {
            var t = options.interaction.redrawOverlayInterval;
            if (t == -1) {
                drawOverlay();
                return
            }
            if (!redrawTimeout)redrawTimeout = setTimeout(drawOverlay, t)
        }

        function drawOverlay() {
            redrawTimeout = null;
            octx.save();
            overlay.clear();
            octx.translate(plotOffset.left, plotOffset.top);
            var i, hi;
            for (i = 0; i < highlights.length; ++i) {
                hi = highlights[i];
                if (hi.series.bars.show)drawBarHighlight(hi.series, hi.point); else drawPointHighlight(hi.series, hi.point)
            }
            octx.restore();
            executeHooks(hooks.drawOverlay, [octx])
        }

        function highlight(s, point, auto) {
            if (typeof s == "number")s = series[s];
            if (typeof point == "number") {
                var ps = s.datapoints.pointsize;
                point = s.datapoints.points.slice(ps * point, ps * (point + 1))
            }
            var i = indexOfHighlight(s, point);
            if (i == -1) {
                highlights.push({series: s, point: point, auto: auto});
                triggerRedrawOverlay()
            } else if (!auto)highlights[i].auto = false
        }

        function unhighlight(s, point) {
            if (s == null && point == null) {
                highlights = [];
                triggerRedrawOverlay();
                return
            }
            if (typeof s == "number")s = series[s];
            if (typeof point == "number") {
                var ps = s.datapoints.pointsize;
                point = s.datapoints.points.slice(ps * point, ps * (point + 1))
            }
            var i = indexOfHighlight(s, point);
            if (i != -1) {
                highlights.splice(i, 1);
                triggerRedrawOverlay()
            }
        }

        function indexOfHighlight(s, p) {
            for (var i = 0; i < highlights.length; ++i) {
                var h = highlights[i];
                if (h.series == s && h.point[0] == p[0] && h.point[1] == p[1])return i
            }
            return-1
        }

        function drawPointHighlight(series, point) {
            var x = point[0], y = point[1], axisx = series.xaxis, axisy = series.yaxis, highlightColor = typeof series.highlightColor === "string" ? series.highlightColor : $.color.parse(series.color).scale("a", .5).toString();
            if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)return;
            var pointRadius = series.points.radius + series.points.lineWidth / 2;
            octx.lineWidth = pointRadius;
            octx.strokeStyle = highlightColor;
            var radius = 1.5 * pointRadius;
            x = axisx.p2c(x);
            y = axisy.p2c(y);
            octx.beginPath();
            if (series.points.symbol == "circle")octx.arc(x, y, radius, 0, 2 * Math.PI, false); else series.points.symbol(octx, x, y, radius, false);
            octx.closePath();
            octx.stroke()
        }

        function drawBarHighlight(series, point) {
            var highlightColor = typeof series.highlightColor === "string" ? series.highlightColor : $.color.parse(series.color).scale("a", .5).toString(), fillStyle = highlightColor, barLeft;
            switch (series.bars.align) {
                case"left":
                    barLeft = 0;
                    break;
                case"right":
                    barLeft = -series.bars.barWidth;
                    break;
                default:
                    barLeft = -series.bars.barWidth / 2
            }
            octx.lineWidth = series.bars.lineWidth;
            octx.strokeStyle = highlightColor;
            drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth, function () {
                return fillStyle
            }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth)
        }

        function getColorOrGradient(spec, bottom, top, defaultColor) {
            if (typeof spec == "string")return spec; else {
                var gradient = ctx.createLinearGradient(0, top, 0, bottom);
                for (var i = 0, l = spec.colors.length; i < l; ++i) {
                    var c = spec.colors[i];
                    if (typeof c != "string") {
                        var co = $.color.parse(defaultColor);
                        if (c.brightness != null)co = co.scale("rgb", c.brightness);
                        if (c.opacity != null)co.a *= c.opacity;
                        c = co.toString()
                    }
                    gradient.addColorStop(i / (l - 1), c)
                }
                return gradient
            }
        }
    }

    $.plot = function (placeholder, data, options) {
        var plot = new Plot($(placeholder), data, options, $.plot.plugins);
        return plot
    };
    $.plot.version = "0.8.2";
    $.plot.plugins = [];
    $.fn.plot = function (data, options) {
        return this.each(function () {
            $.plot(this, data, options)
        })
    };
    function floorInBase(n, base) {
        return base * Math.floor(n / base)
    }
})(jQuery);