Updated JQuery-flot package
authorThierry Florac <tflorac@ulthar.net>
Wed, 16 Oct 2019 13:00:43 +0200
changeset 546 9f02c09d2393
parent 545 f3af77e371a1
child 547 9fe15f541572
Updated JQuery-flot package
src/pyams_skin/resources/js/ext/flot/jquery.canvaswrapper.js
src/pyams_skin/resources/js/ext/flot/jquery.canvaswrapper.min.js
src/pyams_skin/resources/js/ext/flot/jquery.colorhelpers.js
src/pyams_skin/resources/js/ext/flot/jquery.colorhelpers.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.axislabels.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.axislabels.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.browser.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.browser.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.categories.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.categories.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.composeImages.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.composeImages.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.crosshair.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.crosshair.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.drawSeries.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.drawSeries.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.errorbars.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.errorbars.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.fillbetween.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.fillbetween.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.flatdata.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.flatdata.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.hover.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.hover.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.image.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.image.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.legend.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.legend.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.logaxis.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.logaxis.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.navigate.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.navigate.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.pie.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.pie.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.resize.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.resize.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.saturated.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.saturated.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.selection.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.selection.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.stack.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.stack.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.symbol.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.symbol.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.threshold.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.threshold.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.time.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.time.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.touch.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.touch.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.touchNavigate.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.touchNavigate.min.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.uiConstants.js
src/pyams_skin/resources/js/ext/flot/jquery.flot.uiConstants.min.js
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.canvaswrapper.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,550 @@
+/** ## jquery.flot.canvaswrapper
+
+This plugin contains the function for creating and manipulating both the canvas
+layers and svg layers.
+
+The Canvas object is a wrapper around an HTML5 canvas tag.
+The constructor Canvas(cls, container) takes as parameters cls,
+the list of classes to apply to the canvas adnd the containter,
+element onto which to append the canvas. The canvas operations
+don't work unless the canvas is attached to the DOM.
+
+### jquery.canvaswrapper.js API functions
+*/
+
+(function($) {
+    var Canvas = function(cls, container) {
+        var element = container.getElementsByClassName(cls)[0];
+
+        if (!element) {
+            element = document.createElement('canvas');
+            element.className = cls;
+            element.style.direction = 'ltr';
+            element.style.position = 'absolute';
+            element.style.left = '0px';
+            element.style.top = '0px';
+
+            container.appendChild(element);
+
+            // If HTML5 Canvas isn't available, throw
+
+            if (!element.getContext) {
+                throw new Error('Canvas is not available.');
+            }
+        }
+
+        this.element = element;
+
+        var context = this.context = element.getContext('2d');
+        this.pixelRatio = $.plot.browser.getPixelRatio(context);
+
+        // Size the canvas to match the internal dimensions of its container
+        var width = $(container).width();
+        var height = $(container).height();
+        this.resize(width, height);
+
+        // Collection of HTML div layers for text overlaid onto the canvas
+
+        this.SVGContainer = null;
+        this.SVG = {};
+
+        // Cache of text fragments and metrics, so we can avoid expensively
+        // re-calculating them when the plot is re-rendered in a loop.
+
+        this._textCache = {};
+    }
+
+    /**
+    - resize(width, height)
+
+     Resizes the canvas to the given dimensions.
+     The width represents the new width of the canvas, meanwhile the height
+     is the new height of the canvas, both of them in pixels.
+    */
+
+    Canvas.prototype.resize = function(width, height) {
+        var minSize = 10;
+        width = width < minSize ? minSize : width;
+        height = height < minSize ? minSize : height;
+
+        var element = this.element,
+            context = this.context,
+            pixelRatio = this.pixelRatio;
+
+        // Resize the canvas, increasing its density based on the display's
+        // pixel ratio; basically giving it more pixels without increasing the
+        // size of its element, to take advantage of the fact that retina
+        // displays have that many more pixels in the same advertised space.
+
+        // Resizing should reset the state (excanvas seems to be buggy though)
+
+        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;
+        }
+
+        // Save the context, so we can reset in case we get replotted.  The
+        // restore ensure that we're really back at the initial state, and
+        // should be safe even if we haven't saved the initial state yet.
+
+        context.restore();
+        context.save();
+
+        // Scale the coordinate space to match the display density; so even though we
+        // may have twice as many pixels, we still want lines and other drawing to
+        // appear at the same size; the extra pixels will just make them crisper.
+
+        context.scale(pixelRatio, pixelRatio);
+    };
+
+    /**
+    - clear()
+
+     Clears the entire canvas area, not including any overlaid HTML text
+    */
+    Canvas.prototype.clear = function() {
+        this.context.clearRect(0, 0, this.width, this.height);
+    };
+
+    /**
+    - render()
+
+     Finishes rendering the canvas, including managing the text overlay.
+    */
+    Canvas.prototype.render = function() {
+        var cache = this._textCache;
+
+        // For each text layer, add elements marked as active that haven't
+        // already been rendered, and remove those that are no longer active.
+
+        for (var layerKey in cache) {
+            if (hasOwnProperty.call(cache, layerKey)) {
+                var layer = this.getSVGLayer(layerKey),
+                    layerCache = cache[layerKey];
+
+                var display = layer.style.display;
+                layer.style.display = 'none';
+
+                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 val = styleCache[key],
+                                    positions = val.positions;
+
+                                for (var i = 0, position; positions[i]; i++) {
+                                    position = positions[i];
+                                    if (position.active) {
+                                        if (!position.rendered) {
+                                            layer.appendChild(position.element);
+                                            position.rendered = true;
+                                        }
+                                    } else {
+                                        positions.splice(i--, 1);
+                                        if (position.rendered) {
+                                            while (position.element.firstChild) {
+                                                position.element.removeChild(position.element.firstChild);
+                                            }
+                                            position.element.parentNode.removeChild(position.element);
+                                        }
+                                    }
+                                }
+
+                                if (positions.length === 0) {
+                                    if (val.measured) {
+                                        val.measured = false;
+                                    } else {
+                                        delete styleCache[key];
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+
+                layer.style.display = display;
+            }
+        }
+    };
+
+    /**
+    - getSVGLayer(classes)
+
+     Creates (if necessary) and returns the SVG overlay container.
+     The classes string represents the string of space-separated CSS classes
+     used to uniquely identify the text layer. It return the svg-layer div.
+    */
+    Canvas.prototype.getSVGLayer = function(classes) {
+        var layer = this.SVG[classes];
+
+        // Create the SVG layer if it doesn't exist
+
+        if (!layer) {
+            // Create the svg layer container, if it doesn't exist
+
+            var svgElement;
+
+            if (!this.SVGContainer) {
+                this.SVGContainer = document.createElement('div');
+                this.SVGContainer.className = 'flot-svg';
+                this.SVGContainer.style.position = 'absolute';
+                this.SVGContainer.style.top = '0px';
+                this.SVGContainer.style.left = '0px';
+                this.SVGContainer.style.height = '100%';
+                this.SVGContainer.style.width = '100%';
+                this.SVGContainer.style.pointerEvents = 'none';
+                this.element.parentNode.appendChild(this.SVGContainer);
+
+                svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
+                svgElement.style.width = '100%';
+                svgElement.style.height = '100%';
+
+                this.SVGContainer.appendChild(svgElement);
+            } else {
+                svgElement = this.SVGContainer.firstChild;
+            }
+
+            layer = document.createElementNS('http://www.w3.org/2000/svg', 'g');
+            layer.setAttribute('class', classes);
+            layer.style.position = 'absolute';
+            layer.style.top = '0px';
+            layer.style.left = '0px';
+            layer.style.bottom = '0px';
+            layer.style.right = '0px';
+            svgElement.appendChild(layer);
+            this.SVG[classes] = layer;
+        }
+
+        return layer;
+    };
+
+    /**
+    - getTextInfo(layer, text, font, angle, width)
+
+     Creates (if necessary) and returns a text info object.
+     The object looks like this:
+     ```js
+     {
+         width //Width of the text's wrapper div.
+         height //Height of the text's wrapper div.
+         element //The HTML div containing the text.
+         positions //Array of positions at which this text is drawn.
+      }
+      ```
+      The positions array contains objects that look like this:
+      ```js
+      {
+         active //Flag indicating whether the text should be visible.
+         rendered //Flag indicating whether the text is currently visible.
+         element //The HTML div containing the text.
+         text //The actual text and is identical with element[0].textContent.
+         x //X coordinate at which to draw the text.
+         y //Y coordinate at which to draw the text.
+      }
+      ```
+      Each position after the first receives a clone of the original element.
+      The idea is that that the width, height, and general 'identity' of the
+      text is constant no matter where it is placed; the placements are a
+      secondary property.
+
+      Canvas maintains a cache of recently-used text info objects; getTextInfo
+      either returns the cached element or creates a new entry.
+
+     The layer parameter is string of space-separated CSS classes uniquely
+     identifying the layer containing this text.
+     Text is the text string to retrieve info for.
+     Font is either a string of space-separated CSS classes or a font-spec object,
+     defining the text's font and style.
+     Angle is the angle at which to rotate the text, in degrees. Angle is currently unused,
+     it will be implemented in the future.
+     The last parameter is the Maximum width of the text before it wraps.
+     The method returns a text info object.
+    */
+    Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
+        var textStyle, layerCache, styleCache, info;
+
+        // Cast the value to a string, in case we were given a number or such
+
+        text = '' + text;
+
+        // If the font is a font-spec object, generate a CSS font definition
+
+        if (typeof font === 'object') {
+            textStyle = font.style + ' ' + font.variant + ' ' + font.weight + ' ' + font.size + 'px/' + font.lineHeight + 'px ' + font.family;
+        } else {
+            textStyle = font;
+        }
+
+        // Retrieve (or create) the cache for the text's layer and styles
+
+        layerCache = this._textCache[layer];
+
+        if (layerCache == null) {
+            layerCache = this._textCache[layer] = {};
+        }
+
+        styleCache = layerCache[textStyle];
+
+        if (styleCache == null) {
+            styleCache = layerCache[textStyle] = {};
+        }
+
+        var key = generateKey(text);
+        info = styleCache[key];
+
+        // If we can't find a matching element in our cache, create a new one
+
+        if (!info) {
+            var element = document.createElementNS('http://www.w3.org/2000/svg', 'text');
+            if (text.indexOf('<br>') !== -1) {
+                addTspanElements(text, element, -9999);
+            } else {
+                var textNode = document.createTextNode(text);
+                element.appendChild(textNode);
+            }
+
+            element.style.position = 'absolute';
+            element.style.maxWidth = width;
+            element.setAttributeNS(null, 'x', -9999);
+            element.setAttributeNS(null, 'y', -9999);
+
+            if (typeof font === 'object') {
+                element.style.font = textStyle;
+                element.style.fill = font.fill;
+            } else if (typeof font === 'string') {
+                element.setAttribute('class', font);
+            }
+
+            this.getSVGLayer(layer).appendChild(element);
+            var elementRect = element.getBBox();
+
+            info = styleCache[key] = {
+                width: elementRect.width,
+                height: elementRect.height,
+                measured: true,
+                element: element,
+                positions: []
+            };
+
+            //remove elements from dom
+            while (element.firstChild) {
+                element.removeChild(element.firstChild);
+            }
+            element.parentNode.removeChild(element);
+        }
+
+        info.measured = true;
+        return info;
+    };
+
+    function updateTransforms (element, transforms) {
+        element.transform.baseVal.clear();
+        if (transforms) {
+            transforms.forEach(function(t) {
+                element.transform.baseVal.appendItem(t);
+            });
+        }
+    }
+
+    /**
+    - addText (layer, x, y, text, font, angle, width, halign, valign, transforms)
+
+     Adds a text string to the canvas text overlay.
+     The text isn't drawn immediately; it is marked as rendering, which will
+     result in its addition to the canvas on the next render pass.
+
+     The layer is string of space-separated CSS classes uniquely
+     identifying the layer containing this text.
+     X and Y represents the X and Y coordinate at which to draw the text.
+     and text is the string to draw
+    */
+    Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign, transforms) {
+        var info = this.getTextInfo(layer, text, font, angle, width),
+            positions = info.positions;
+
+        // Tweak the div's position to match the text's alignment
+
+        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;
+        }
+
+        y += 0.75 * info.height;
+
+
+        // Determine whether this text already exists at this position.
+        // If so, mark it for inclusion in the next render pass.
+
+        for (var i = 0, position; positions[i]; i++) {
+            position = positions[i];
+            if (position.x === x && position.y === y && position.text === text) {
+                position.active = true;
+                // update the transforms
+                updateTransforms(position.element, transforms);
+
+                return;
+            } else if (position.active === false) {
+                position.active = true;
+                position.text = text;
+                if (text.indexOf('<br>') !== -1) {
+                    y -= 0.25 * info.height;
+                    addTspanElements(text, position.element, x);
+                } else {
+                    position.element.textContent = text;
+                }
+                position.element.setAttributeNS(null, 'x', x);
+                position.element.setAttributeNS(null, 'y', y);
+                position.x = x;
+                position.y = y;
+                // update the transforms
+                updateTransforms(position.element, transforms);
+
+                return;
+            }
+        }
+
+        // If the text doesn't exist at this position, create a new entry
+
+        // For the very first position we'll re-use the original element,
+        // while for subsequent ones we'll clone it.
+
+        position = {
+            active: true,
+            rendered: false,
+            element: positions.length ? info.element.cloneNode() : info.element,
+            text: text,
+            x: x,
+            y: y
+        };
+
+        positions.push(position);
+
+        if (text.indexOf('<br>') !== -1) {
+            y -= 0.25 * info.height;
+            addTspanElements(text, position.element, x);
+        } else {
+            position.element.textContent = text;
+        }
+
+        // Move the element to its final position within the container
+        position.element.setAttributeNS(null, 'x', x);
+        position.element.setAttributeNS(null, 'y', y);
+        position.element.style.textAlign = halign;
+        // update the transforms
+        updateTransforms(position.element, transforms);
+   };
+
+    var addTspanElements = function(text, element, x) {
+        var lines = text.split('<br>'),
+            tspan, i, offset;
+
+        for (i = 0; i < lines.length; i++) {
+            if (!element.childNodes[i]) {
+                tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
+                element.appendChild(tspan);
+            } else {
+                tspan = element.childNodes[i];
+            }
+            tspan.textContent = lines[i];
+            offset = i * 1 + 'em';
+            tspan.setAttributeNS(null, 'dy', offset);
+            tspan.setAttributeNS(null, 'x', x);
+        }
+    }
+
+    /**
+    - removeText (layer, x, y, text, font, angle)
+
+      The function removes one or more text strings from the canvas text overlay.
+      If no parameters are given, all text within the layer is removed.
+
+      Note that the text is not immediately removed; it is simply marked as
+      inactive, which will result in its removal on the next render pass.
+      This avoids the performance penalty for 'clear and redraw' behavior,
+      where we potentially get rid of all text on a layer, but will likely
+      add back most or all of it later, as when redrawing axes, for example.
+
+      The layer is a string of space-separated CSS classes uniquely
+      identifying the layer containing this text. The following parameter are
+      X and Y coordinate of the text.
+      Text is the string to remove, while the font is either a string of space-separated CSS
+      classes or a font-spec object, defining the text's font and style.
+     */
+    Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
+        var info, htmlYCoord;
+        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;
+                                positions.forEach(function(position) {
+                                    position.active = false;
+                                });
+                            }
+                        }
+                    }
+                }
+            }
+        } else {
+            info = this.getTextInfo(layer, text, font, angle);
+            positions = info.positions;
+            positions.forEach(function(position) {
+                htmlYCoord = y + 0.75 * info.height;
+                if (position.x === x && position.y === htmlYCoord && position.text === text) {
+                    position.active = false;
+                }
+            });
+        }
+    };
+
+    /**
+    - clearCache()
+
+     Clears the cache used to speed up the text size measurements.
+     As an (unfortunate) side effect all text within the text Layer is removed.
+     Use this function before plot.setupGrid() and plot.draw() if the plot just
+     became visible or the styles changed.
+    */
+    Canvas.prototype.clearCache = function() {
+        var cache = this._textCache;
+        for (var layerKey in cache) {
+            if (hasOwnProperty.call(cache, layerKey)) {
+                var layer = this.getSVGLayer(layerKey);
+                while (layer.firstChild) {
+                    layer.removeChild(layer.firstChild);
+                }
+            }
+        };
+
+        this._textCache = {};
+    };
+
+    function generateKey(text) {
+        return text.replace(/0|1|2|3|4|5|6|7|8|9/g, '0');
+    }
+
+    if (!window.Flot) {
+        window.Flot = {};
+    }
+
+    window.Flot.Canvas = Canvas;
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.canvaswrapper.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(t){function e(t,e){t.transform.baseVal.clear(),e&&e.forEach(function(e){t.transform.baseVal.appendItem(e)})}function i(t){return t.replace(/0|1|2|3|4|5|6|7|8|9/g,"0")}var n=function(e,i){var n=i.getElementsByClassName(e)[0];if(!n&&(n=document.createElement("canvas"),n.className=e,n.style.direction="ltr",n.style.position="absolute",n.style.left="0px",n.style.top="0px",i.appendChild(n),!n.getContext))throw new Error("Canvas is not available.");this.element=n;var r=this.context=n.getContext("2d");this.pixelRatio=t.plot.browser.getPixelRatio(r);var l=t(i).width(),o=t(i).height();this.resize(l,o),this.SVGContainer=null,this.SVG={},this._textCache={}};n.prototype.resize=function(t,e){t=t<10?10:t,e=e<10?10:e;var i=this.element,n=this.context,r=this.pixelRatio;this.width!==t&&(i.width=t*r,i.style.width=t+"px",this.width=t),this.height!==e&&(i.height=e*r,i.style.height=e+"px",this.height=e),n.restore(),n.save(),n.scale(r,r)},n.prototype.clear=function(){this.context.clearRect(0,0,this.width,this.height)},n.prototype.render=function(){var t=this._textCache;for(var e in t)if(hasOwnProperty.call(t,e)){var i=this.getSVGLayer(e),n=t[e],r=i.style.display;i.style.display="none";for(var l in n)if(hasOwnProperty.call(n,l)){var o=n[l];for(var s in o)if(hasOwnProperty.call(o,s)){for(var a,h=o[s],d=h.positions,p=0;d[p];p++)if((a=d[p]).active)a.rendered||(i.appendChild(a.element),a.rendered=!0);else if(d.splice(p--,1),a.rendered){for(;a.element.firstChild;)a.element.removeChild(a.element.firstChild);a.element.parentNode.removeChild(a.element)}0===d.length&&(h.measured?h.measured=!1:delete o[s])}}i.style.display=r}},n.prototype.getSVGLayer=function(t){var e=this.SVG[t];if(!e){var i;this.SVGContainer?i=this.SVGContainer.firstChild:(this.SVGContainer=document.createElement("div"),this.SVGContainer.className="flot-svg",this.SVGContainer.style.position="absolute",this.SVGContainer.style.top="0px",this.SVGContainer.style.left="0px",this.SVGContainer.style.height="100%",this.SVGContainer.style.width="100%",this.SVGContainer.style.pointerEvents="none",this.element.parentNode.appendChild(this.SVGContainer),(i=document.createElementNS("http://www.w3.org/2000/svg","svg")).style.width="100%",i.style.height="100%",this.SVGContainer.appendChild(i)),(e=document.createElementNS("http://www.w3.org/2000/svg","g")).setAttribute("class",t),e.style.position="absolute",e.style.top="0px",e.style.left="0px",e.style.bottom="0px",e.style.right="0px",i.appendChild(e),this.SVG[t]=e}return e},n.prototype.getTextInfo=function(t,e,n,l,o){var s,a,h,d;e=""+e,s="object"==typeof n?n.style+" "+n.variant+" "+n.weight+" "+n.size+"px/"+n.lineHeight+"px "+n.family:n,null==(a=this._textCache[t])&&(a=this._textCache[t]={}),null==(h=a[s])&&(h=a[s]={});var p=i(e);if(!(d=h[p])){var c=document.createElementNS("http://www.w3.org/2000/svg","text");if(-1!==e.indexOf("<br>"))r(e,c,-9999);else{var f=document.createTextNode(e);c.appendChild(f)}c.style.position="absolute",c.style.maxWidth=o,c.setAttributeNS(null,"x",-9999),c.setAttributeNS(null,"y",-9999),"object"==typeof n?(c.style.font=s,c.style.fill=n.fill):"string"==typeof n&&c.setAttribute("class",n),this.getSVGLayer(t).appendChild(c);var y=c.getBBox();for(d=h[p]={width:y.width,height:y.height,measured:!0,element:c,positions:[]};c.firstChild;)c.removeChild(c.firstChild);c.parentNode.removeChild(c)}return d.measured=!0,d},n.prototype.addText=function(t,i,n,l,o,s,a,h,d,p){var c=this.getTextInfo(t,l,o,s,a),f=c.positions;"center"===h?i-=c.width/2:"right"===h&&(i-=c.width),"middle"===d?n-=c.height/2:"bottom"===d&&(n-=c.height),n+=.75*c.height;for(var y,m=0;f[m];m++){if((y=f[m]).x===i&&y.y===n&&y.text===l)return y.active=!0,void e(y.element,p);if(!1===y.active)return y.active=!0,y.text=l,-1!==l.indexOf("<br>")?(n-=.25*c.height,r(l,y.element,i)):y.element.textContent=l,y.element.setAttributeNS(null,"x",i),y.element.setAttributeNS(null,"y",n),y.x=i,y.y=n,void e(y.element,p)}y={active:!0,rendered:!1,element:f.length?c.element.cloneNode():c.element,text:l,x:i,y:n},f.push(y),-1!==l.indexOf("<br>")?(n-=.25*c.height,r(l,y.element,i)):y.element.textContent=l,y.element.setAttributeNS(null,"x",i),y.element.setAttributeNS(null,"y",n),y.element.style.textAlign=h,e(y.element,p)};var r=function(t,e,i){var n,r,l,o=t.split("<br>");for(r=0;r<o.length;r++)e.childNodes[r]?n=e.childNodes[r]:(n=document.createElementNS("http://www.w3.org/2000/svg","tspan"),e.appendChild(n)),n.textContent=o[r],l=1*r+"em",n.setAttributeNS(null,"dy",l),n.setAttributeNS(null,"x",i)};n.prototype.removeText=function(t,e,i,n,r,l){var o,s;if(null==n){var a=this._textCache[t];if(null!=a)for(var h in a)if(hasOwnProperty.call(a,h)){var d=a[h];for(var p in d)if(hasOwnProperty.call(d,p)){var c=d[p].positions;c.forEach(function(t){t.active=!1})}}}else(c=(o=this.getTextInfo(t,n,r,l)).positions).forEach(function(t){s=i+.75*o.height,t.x===e&&t.y===s&&t.text===n&&(t.active=!1)})},n.prototype.clearCache=function(){var t=this._textCache;for(var e in t)if(hasOwnProperty.call(t,e))for(var i=this.getSVGLayer(e);i.firstChild;)i.removeChild(i.firstChild);this._textCache={}},window.Flot||(window.Flot={}),window.Flot.Canvas=n}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.colorhelpers.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.colorhelpers.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,7 +1,7 @@
 /* Plugin for jQuery for working with colors.
- * 
+ *
  * Version 1.1.
- * 
+ *
  * Inspiration from jQuery color animation plugin by John Resig.
  *
  * Released under the MIT license by Ole Laursen, October 2009.
@@ -18,7 +18,7 @@
  *
  * V. 1.1: Fix error handling so e.g. parsing an empty string does
  * produce a color rather than just crashing.
- */ 
+ */
 
 (function($) {
     $.color = {};
@@ -32,30 +32,34 @@
         o.a = a != null ? a : 1;
 
         o.add = function (c, d) {
-            for (var i = 0; i < c.length; ++i)
+            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)
+            for (var i = 0; i < c.length; ++i) {
                 o[c.charAt(i)] *= f;
+            }
+
             return o.normalize();
         };
-        
+
         o.toString = function () {
             if (o.a >= 1.0) {
-                return "rgb("+[o.r, o.g, o.b].join(",")+")";
+                return "rgb(" + [o.r, o.g, o.b].join(",") + ")";
             } else {
-                return "rgba("+[o.r, o.g, o.b, o.a].join(",")+")";
+                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);
+                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);
@@ -79,18 +83,21 @@
             c = elem.css(css).toLowerCase();
             // keep going until we find an element that has color, or
             // we hit the body or root (have no parent)
-            if (c != '' && c != 'transparent')
+            if (c !== '' && c !== 'transparent') {
                 break;
+            }
+
             elem = elem.parent();
         } while (elem.length && !$.nodeName(elem.get(0), "body"));
 
         // catch Safari's way of signalling transparent
-        if (c == "rgba(0, 0, 0, 0)")
+        if (c === "rgba(0, 0, 0, 0)") {
             c = "transparent";
-        
+        }
+
         return $.color.parse(c);
     }
-    
+
     // parse CSS color string (like "rgb(10, 32, 43)" or "#fff"),
     // returns color object, if parsing failed, you get black (0, 0,
     // 0) out
@@ -98,83 +105,95 @@
         var res, m = $.color.make;
 
         // Look for rgb(num,num,num)
-        if (res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))
+        res = /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str);
+        if (res) {
             return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10));
-        
+        }
+
         // Look for rgba(num,num,num,num)
-        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))
+        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)
+        if (res) {
             return m(parseInt(res[1], 10), parseInt(res[2], 10), parseInt(res[3], 10), parseFloat(res[4]));
-            
+        }
+
         // Look for rgb(num%,num%,num%)
-        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);
+        res = /rgb\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*\)/.exec(str);
+        if (res) {
+            return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55);
+        }
 
         // Look for rgba(num%,num%,num%,num)
-        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]));
-        
+        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);
+        if (res) {
+            return m(parseFloat(res[1]) * 2.55, parseFloat(res[2]) * 2.55, parseFloat(res[3]) * 2.55, parseFloat(res[4]));
+        }
+
         // Look for #a0b1c2
-        if (res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))
+        res = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str);
+        if (res) {
             return m(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16));
+        }
 
         // Look for #fff
-        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));
+        res = /#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str);
+        if (res) {
+            return m(parseInt(res[1] + res[1], 16), parseInt(res[2] + res[2], 16), parseInt(res[3] + res[3], 16));
+        }
 
         // Otherwise, we're most likely dealing with a named color
         var name = $.trim(str).toLowerCase();
-        if (name == "transparent")
+        if (name === "transparent") {
             return m(255, 255, 255, 0);
-        else {
+        } else {
             // default to black
             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]
+        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);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.colorhelpers.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.colorhelpers.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,1 +1,1 @@
-!function(r){r.color={},r.color.make=function(e,a,n,t){var o={};return o.r=e||0,o.g=a||0,o.b=n||0,o.a=null!=t?t:1,o.add=function(r,e){for(var a=0;a<r.length;++a)o[r.charAt(a)]+=e;return o.normalize()},o.scale=function(r,e){for(var a=0;a<r.length;++a)o[r.charAt(a)]*=e;return o.normalize()},o.toString=function(){return o.a>=1?"rgb("+[o.r,o.g,o.b].join(",")+")":"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"},o.normalize=function(){function r(r,e,a){return e<r?r:e>a?a:e}return o.r=r(0,parseInt(o.r),255),o.g=r(0,parseInt(o.g),255),o.b=r(0,parseInt(o.b),255),o.a=r(0,o.a,1),o},o.clone=function(){return r.color.make(o.r,o.b,o.g,o.a)},o.normalize()},r.color.extract=function(e,a){var n;do{if(""!=(n=e.css(a).toLowerCase())&&"transparent"!=n)break;e=e.parent()}while(e.length&&!r.nodeName(e.get(0),"body"));return"rgba(0, 0, 0, 0)"==n&&(n="transparent"),r.color.parse(n)},r.color.parse=function(a){var n,t=r.color.make;if(n=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))return t(parseInt(n[1],10),parseInt(n[2],10),parseInt(n[3],10));if(n=/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(a))return t(parseInt(n[1],10),parseInt(n[2],10),parseInt(n[3],10),parseFloat(n[4]));if(n=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(a))return t(2.55*parseFloat(n[1]),2.55*parseFloat(n[2]),2.55*parseFloat(n[3]));if(n=/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(a))return t(2.55*parseFloat(n[1]),2.55*parseFloat(n[2]),2.55*parseFloat(n[3]),parseFloat(n[4]));if(n=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))return t(parseInt(n[1],16),parseInt(n[2],16),parseInt(n[3],16));if(n=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(a))return t(parseInt(n[1]+n[1],16),parseInt(n[2]+n[2],16),parseInt(n[3]+n[3],16));var o=r.trim(a).toLowerCase();return"transparent"==o?t(255,255,255,0):(n=e[o]||[0,0,0],t(n[0],n[1],n[2]))};var e={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(r){r.color={},r.color.make=function(e,a,n,t){var o={};return o.r=e||0,o.g=a||0,o.b=n||0,o.a=null!=t?t:1,o.add=function(r,e){for(var a=0;a<r.length;++a)o[r.charAt(a)]+=e;return o.normalize()},o.scale=function(r,e){for(var a=0;a<r.length;++a)o[r.charAt(a)]*=e;return o.normalize()},o.toString=function(){return o.a>=1?"rgb("+[o.r,o.g,o.b].join(",")+")":"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"},o.normalize=function(){function r(r,e,a){return e<r?r:e>a?a:e}return o.r=r(0,parseInt(o.r),255),o.g=r(0,parseInt(o.g),255),o.b=r(0,parseInt(o.b),255),o.a=r(0,o.a,1),o},o.clone=function(){return r.color.make(o.r,o.b,o.g,o.a)},o.normalize()},r.color.extract=function(e,a){var n;do{if(""!==(n=e.css(a).toLowerCase())&&"transparent"!==n)break;e=e.parent()}while(e.length&&!r.nodeName(e.get(0),"body"));return"rgba(0, 0, 0, 0)"===n&&(n="transparent"),r.color.parse(n)},r.color.parse=function(a){var n,t=r.color.make;if(n=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(a))return t(parseInt(n[1],10),parseInt(n[2],10),parseInt(n[3],10));if(n=/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(a))return t(parseInt(n[1],10),parseInt(n[2],10),parseInt(n[3],10),parseFloat(n[4]));if(n=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*,\s*([0-9]+(?:\.[0-9]+)?)%\s*\)/.exec(a))return t(2.55*parseFloat(n[1]),2.55*parseFloat(n[2]),2.55*parseFloat(n[3]));if(n=/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(a))return t(2.55*parseFloat(n[1]),2.55*parseFloat(n[2]),2.55*parseFloat(n[3]),parseFloat(n[4]));if(n=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))return t(parseInt(n[1],16),parseInt(n[2],16),parseInt(n[3],16));if(n=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(a))return t(parseInt(n[1]+n[1],16),parseInt(n[2]+n[2],16),parseInt(n[3]+n[3],16));var o=r.trim(a).toLowerCase();return"transparent"===o?t(255,255,255,0):(n=e[o]||[0,0,0],t(n[0],n[1],n[2]))};var e={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);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.axislabels.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,212 @@
+/*
+Axis label plugin for flot
+
+Derived from:
+Axis Labels Plugin for flot.
+http://github.com/markrcote/flot-axislabels
+
+Original code is Copyright (c) 2010 Xuan Luo.
+Original code was released under the GPLv3 license by Xuan Luo, September 2010.
+Original code was rereleased under the MIT license by Xuan Luo, April 2012.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+(function($) {
+    "use strict";
+
+    var options = {
+        axisLabels: {
+            show: true
+        }
+    };
+
+    function AxisLabel(axisName, position, padding, placeholder, axisLabel, surface) {
+        this.axisName = axisName;
+        this.position = position;
+        this.padding = padding;
+        this.placeholder = placeholder;
+        this.axisLabel = axisLabel;
+        this.surface = surface;
+        this.width = 0;
+        this.height = 0;
+        this.elem = null;
+    }
+
+    AxisLabel.prototype.calculateSize = function() {
+        var axisId = this.axisName + 'Label',
+            layerId = axisId + 'Layer',
+            className = axisId + ' axisLabels';
+
+        var info = this.surface.getTextInfo(layerId, this.axisLabel, className);
+        this.labelWidth = info.width;
+        this.labelHeight = info.height;
+
+        if (this.position === 'left' || this.position === 'right') {
+            this.width = this.labelHeight + this.padding;
+            this.height = 0;
+        } else {
+            this.width = 0;
+            this.height = this.labelHeight + this.padding;
+        }
+    };
+
+    AxisLabel.prototype.transforms = function(degrees, x, y, svgLayer) {
+        var transforms = [], translate, rotate;
+        if (x !== 0 || y !== 0) {
+            translate = svgLayer.createSVGTransform();
+            translate.setTranslate(x, y);
+            transforms.push(translate);
+        }
+        if (degrees !== 0) {
+            rotate = svgLayer.createSVGTransform();
+            var centerX = Math.round(this.labelWidth / 2),
+                centerY = 0;
+            rotate.setRotate(degrees, centerX, centerY);
+            transforms.push(rotate);
+        }
+
+        return transforms;
+    };
+
+    AxisLabel.prototype.calculateOffsets = function(box) {
+        var offsets = {
+            x: 0,
+            y: 0,
+            degrees: 0
+        };
+        if (this.position === 'bottom') {
+            offsets.x = box.left + box.width / 2 - this.labelWidth / 2;
+            offsets.y = box.top + box.height - this.labelHeight;
+        } else if (this.position === 'top') {
+            offsets.x = box.left + box.width / 2 - this.labelWidth / 2;
+            offsets.y = box.top;
+        } else if (this.position === 'left') {
+            offsets.degrees = -90;
+            offsets.x = box.left - this.labelWidth / 2;
+            offsets.y = box.height / 2 + box.top;
+        } else if (this.position === 'right') {
+            offsets.degrees = 90;
+            offsets.x = box.left + box.width - this.labelWidth / 2;
+            offsets.y = box.height / 2 + box.top;
+        }
+        offsets.x = Math.round(offsets.x);
+        offsets.y = Math.round(offsets.y);
+
+        return offsets;
+    };
+
+    AxisLabel.prototype.cleanup = function() {
+        var axisId = this.axisName + 'Label',
+            layerId = axisId + 'Layer',
+            className = axisId + ' axisLabels';
+        this.surface.removeText(layerId, 0, 0, this.axisLabel, className);
+    };
+
+    AxisLabel.prototype.draw = function(box) {
+        var axisId = this.axisName + 'Label',
+            layerId = axisId + 'Layer',
+            className = axisId + ' axisLabels',
+            offsets = this.calculateOffsets(box),
+            style = {
+                position: 'absolute',
+                bottom: '',
+                right: '',
+                display: 'inline-block',
+                'white-space': 'nowrap'
+            };
+
+        var layer = this.surface.getSVGLayer(layerId);
+        var transforms = this.transforms(offsets.degrees, offsets.x, offsets.y, layer.parentNode);
+
+        this.surface.addText(layerId, 0, 0, this.axisLabel, className, undefined, undefined, undefined, undefined, transforms);
+        this.surface.render();
+        Object.keys(style).forEach(function(key) {
+            layer.style[key] = style[key];
+        });
+    };
+
+    function init(plot) {
+        plot.hooks.processOptions.push(function(plot, options) {
+            if (!options.axisLabels.show) {
+                return;
+            }
+
+            var axisLabels = {};
+            var defaultPadding = 2; // padding between axis and tick labels
+
+            plot.hooks.axisReserveSpace.push(function(plot, axis) {
+                var opts = axis.options;
+                var axisName = axis.direction + axis.n;
+
+                axis.labelHeight += axis.boxPosition.centerY;
+                axis.labelWidth += axis.boxPosition.centerX;
+
+                if (!opts || !opts.axisLabel || !axis.show) {
+                    return;
+                }
+
+                var padding = opts.axisLabelPadding === undefined
+                    ? defaultPadding
+                    : opts.axisLabelPadding;
+
+                var axisLabel = axisLabels[axisName];
+                if (!axisLabel) {
+                    axisLabel = new AxisLabel(axisName,
+                        opts.position, padding,
+                        plot.getPlaceholder()[0], opts.axisLabel, plot.getSurface());
+                    axisLabels[axisName] = axisLabel;
+                }
+
+                axisLabel.calculateSize();
+
+                // Incrementing the sizes of the tick labels.
+                axis.labelHeight += axisLabel.height;
+                axis.labelWidth += axisLabel.width;
+            });
+
+            // TODO - use the drawAxis hook
+            plot.hooks.draw.push(function(plot, ctx) {
+                $.each(plot.getAxes(), function(flotAxisName, axis) {
+                    var opts = axis.options;
+                    if (!opts || !opts.axisLabel || !axis.show) {
+                        return;
+                    }
+
+                    var axisName = axis.direction + axis.n;
+                    axisLabels[axisName].draw(axis.box);
+                });
+            });
+
+            plot.hooks.shutdown.push(function(plot, eventHolder) {
+                for (var axisName in axisLabels) {
+                    axisLabels[axisName].cleanup();
+                }
+            });
+        });
+    };
+
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: 'axisLabels',
+        version: '3.0'
+    });
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.axislabels.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(t){"use strict";function i(t,i,e,s,a,h){this.axisName=t,this.position=i,this.padding=e,this.placeholder=s,this.axisLabel=a,this.surface=h,this.width=0,this.height=0,this.elem=null}i.prototype.calculateSize=function(){var t=this.axisName+"Label",i=t+"Layer",e=t+" axisLabels",s=this.surface.getTextInfo(i,this.axisLabel,e);this.labelWidth=s.width,this.labelHeight=s.height,"left"===this.position||"right"===this.position?(this.width=this.labelHeight+this.padding,this.height=0):(this.width=0,this.height=this.labelHeight+this.padding)},i.prototype.transforms=function(t,i,e,s){var a,h,o=[];if(0===i&&0===e||((a=s.createSVGTransform()).setTranslate(i,e),o.push(a)),0!==t){h=s.createSVGTransform();var n=Math.round(this.labelWidth/2);h.setRotate(t,n,0),o.push(h)}return o},i.prototype.calculateOffsets=function(t){var i={x:0,y:0,degrees:0};return"bottom"===this.position?(i.x=t.left+t.width/2-this.labelWidth/2,i.y=t.top+t.height-this.labelHeight):"top"===this.position?(i.x=t.left+t.width/2-this.labelWidth/2,i.y=t.top):"left"===this.position?(i.degrees=-90,i.x=t.left-this.labelWidth/2,i.y=t.height/2+t.top):"right"===this.position&&(i.degrees=90,i.x=t.left+t.width-this.labelWidth/2,i.y=t.height/2+t.top),i.x=Math.round(i.x),i.y=Math.round(i.y),i},i.prototype.cleanup=function(){var t=this.axisName+"Label",i=t+"Layer",e=t+" axisLabels";this.surface.removeText(i,0,0,this.axisLabel,e)},i.prototype.draw=function(t){var i=this.axisName+"Label",e=i+"Layer",s=i+" axisLabels",a=this.calculateOffsets(t),h={position:"absolute",bottom:"",right:"",display:"inline-block","white-space":"nowrap"},o=this.surface.getSVGLayer(e),n=this.transforms(a.degrees,a.x,a.y,o.parentNode);this.surface.addText(e,0,0,this.axisLabel,s,void 0,void 0,void 0,void 0,n),this.surface.render(),Object.keys(h).forEach(function(t){o.style[t]=h[t]})},t.plot.plugins.push({init:function(e){e.hooks.processOptions.push(function(e,s){if(s.axisLabels.show){var a={};e.hooks.axisReserveSpace.push(function(t,e){var s=e.options,h=e.direction+e.n;if(e.labelHeight+=e.boxPosition.centerY,e.labelWidth+=e.boxPosition.centerX,s&&s.axisLabel&&e.show){var o=void 0===s.axisLabelPadding?2:s.axisLabelPadding,n=a[h];n||(n=new i(h,s.position,o,t.getPlaceholder()[0],s.axisLabel,t.getSurface()),a[h]=n),n.calculateSize(),e.labelHeight+=n.height,e.labelWidth+=n.width}}),e.hooks.draw.push(function(i,e){t.each(i.getAxes(),function(t,i){var e=i.options;if(e&&e.axisLabel&&i.show){var s=i.direction+i.n;a[s].draw(i.box)}})}),e.hooks.shutdown.push(function(t,i){for(var e in a)a[e].cleanup()})}})},options:{axisLabels:{show:!0}},name:"axisLabels",version:"3.0"})}(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.browser.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,98 @@
+/** ## jquery.flot.browser.js
+
+This plugin is used to make available some browser-related utility functions.
+
+### Methods
+*/
+
+(function ($) {
+    'use strict';
+
+    var browser = {
+        /**
+        - getPageXY(e)
+
+         Calculates the pageX and pageY using the screenX, screenY properties of the event
+         and the scrolling of the page. This is needed because the pageX and pageY
+         properties of the event are not correct while running tests in Edge. */
+        getPageXY: function (e) {
+            // This code is inspired from https://stackoverflow.com/a/3464890
+            var doc = document.documentElement,
+                pageX = e.clientX + (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
+                pageY = e.clientY + (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0);
+            return { X: pageX, Y: pageY };
+        },
+
+        /**
+        - getPixelRatio(context)
+
+         This function returns the current pixel ratio defined by the product of desktop
+         zoom and page zoom.
+         Additional info: https://www.html5rocks.com/en/tutorials/canvas/hidpi/
+        */
+        getPixelRatio: function(context) {
+            var devicePixelRatio = window.devicePixelRatio || 1,
+                backingStoreRatio =
+                context.webkitBackingStorePixelRatio ||
+                context.mozBackingStorePixelRatio ||
+                context.msBackingStorePixelRatio ||
+                context.oBackingStorePixelRatio ||
+                context.backingStorePixelRatio || 1;
+            return devicePixelRatio / backingStoreRatio;
+        },
+
+        /**
+        - isSafari, isMobileSafari, isOpera, isFirefox, isIE, isEdge, isChrome, isBlink
+
+         This is a collection of functions, used to check if the code is running in a
+         particular browser or Javascript engine.
+        */
+        isSafari: function() {
+            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
+            // Safari 3.0+ "[object HTMLElementConstructor]"
+            return /constructor/i.test(window.top.HTMLElement) || (function (p) { return p.toString() === "[object SafariRemoteNotification]"; })(!window.top['safari'] || (typeof window.top.safari !== 'undefined' && window.top.safari.pushNotification));
+        },
+
+        isMobileSafari: function() {
+            //isMobileSafari adapted from https://stackoverflow.com/questions/3007480/determine-if-user-navigated-from-mobile-safari
+            return navigator.userAgent.match(/(iPod|iPhone|iPad)/) && navigator.userAgent.match(/AppleWebKit/);
+        },
+
+        isOpera: function() {
+            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
+            //Opera 8.0+
+            return (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
+        },
+
+        isFirefox: function() {
+            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
+            // Firefox 1.0+
+            return typeof InstallTrigger !== 'undefined';
+        },
+
+        isIE: function() {
+            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
+            // Internet Explorer 6-11
+            return /*@cc_on!@*/false || !!document.documentMode;
+        },
+
+        isEdge: function() {
+            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
+            // Edge 20+
+            return !browser.isIE() && !!window.StyleMedia;
+        },
+
+        isChrome: function() {
+            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
+            // Chrome 1+
+            return !!window.chrome && !!window.chrome.webstore;
+        },
+
+        isBlink: function() {
+            // *** https://stackoverflow.com/questions/9847580/how-to-detect-safari-chrome-ie-firefox-and-opera-browser
+            return (browser.isChrome() || browser.isOpera()) && !!window.CSS;
+        }
+    };
+
+    $.plot.browser = browser;
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.browser.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(i){"use strict";var e={getPageXY:function(i){var e=document.documentElement;return{X:i.clientX+(window.pageXOffset||e.scrollLeft)-(e.clientLeft||0),Y:i.clientY+(window.pageYOffset||e.scrollTop)-(e.clientTop||0)}},getPixelRatio:function(i){return(window.devicePixelRatio||1)/(i.webkitBackingStorePixelRatio||i.mozBackingStorePixelRatio||i.msBackingStorePixelRatio||i.oBackingStorePixelRatio||i.backingStorePixelRatio||1)},isSafari:function(){return/constructor/i.test(window.top.HTMLElement)||"[object SafariRemoteNotification]"===(!window.top["safari"]||typeof window.top.safari!=="undefined"&&window.top.safari.pushNotification).toString()},isMobileSafari:function(){return navigator.userAgent.match(/(iPod|iPhone|iPad)/)&&navigator.userAgent.match(/AppleWebKit/)},isOpera:function(){return!!window.opr&&!!opr.addons||!!window.opera||navigator.userAgent.indexOf(" OPR/")>=0},isFirefox:function(){return"undefined"!=typeof InstallTrigger},isIE:function(){return!!document.documentMode},isEdge:function(){return!e.isIE()&&!!window.StyleMedia},isChrome:function(){return!!window.chrome&&!!window.chrome.webstore},isBlink:function(){return(e.isChrome()||e.isOpera())&&!!window.CSS}};jQuery.plot.browser=e}();
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.categories.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.categories.js	Wed Oct 16 13:00:43 2019 +0200
@@ -9,24 +9,24 @@
 To enable it, you must specify mode: "categories" on the axis with the textual
 labels, e.g.
 
-	$.plot("#placeholder", data, { xaxis: { mode: "categories" } });
+    $.plot("#placeholder", data, { xaxis: { mode: "categories" } });
 
 By default, the labels are ordered as they are met in the data series. If you
 need a different ordering, you can specify "categories" on the axis options
 and list the categories there:
 
-	xaxis: {
-		mode: "categories",
-		categories: ["February", "March", "April"]
-	}
+    xaxis: {
+        mode: "categories",
+        categories: ["February", "March", "April"]
+    }
 
 If you need to customize the distances between the categories, you can specify
 "categories" as an object mapping labels to values
 
-	xaxis: {
-		mode: "categories",
-		categories: { "February": 1, "March": 3, "April": 4 }
-	}
+    xaxis: {
+        mode: "categories",
+        categories: { "February": 1, "March": 3, "April": 4 }
+    }
 
 If you don't specify all categories, the remaining categories will be numbered
 from the max value plus 1 (with a spacing of 1 between each).
@@ -52,17 +52,18 @@
             categories: null
         }
     };
-    
+
     function processRawData(plot, series, data, datapoints) {
         // if categories are enabled, we need to disable
         // auto-transformation to numbers so the strings are intact
         // for later processing
 
-        var xCategories = series.xaxis.options.mode == "categories",
-            yCategories = series.yaxis.options.mode == "categories";
-        
-        if (!(xCategories || yCategories))
+        var xCategories = series.xaxis.options.mode === "categories",
+            yCategories = series.yaxis.options.mode === "categories";
+
+        if (!(xCategories || yCategories)) {
             return;
+        }
 
         var format = datapoints.format;
 
@@ -70,36 +71,41 @@
             // FIXME: auto-detection should really not be defined here
             var s = series;
             format = [];
-            format.push({ x: true, number: true, required: true });
-            format.push({ y: true, number: true, required: true });
+            format.push({ x: true, number: true, required: true, computeRange: true});
+            format.push({ y: true, number: true, required: true, computeRange: 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 });
+                var autoScale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
+                format.push({ y: true, number: true, required: false, defaultValue: 0, computeRange: autoScale });
                 if (s.bars.horizontal) {
                     delete format[format.length - 1].y;
                     format[format.length - 1].x = true;
                 }
             }
-            
+
             datapoints.format = format;
         }
 
         for (var m = 0; m < format.length; ++m) {
-            if (format[m].x && xCategories)
+            if (format[m].x && xCategories) {
                 format[m].number = false;
-            
-            if (format[m].y && yCategories)
+            }
+
+            if (format[m].y && yCategories) {
                 format[m].number = false;
+                format[m].computeRange = false;
+            }
         }
     }
 
     function getNextIndex(categories) {
         var index = -1;
-        
-        for (var v in categories)
-            if (categories[v] > index)
+
+        for (var v in categories) {
+            if (categories[v] > index) {
                 index = categories[v];
+            }
+        }
 
         return index + 1;
     }
@@ -108,41 +114,45 @@
         var res = [];
         for (var label in axis.categories) {
             var v = axis.categories[label];
-            if (v >= axis.min && v <= axis.max)
+            if (v >= axis.min && v <= axis.max) {
                 res.push([v, label]);
+            }
         }
 
         res.sort(function (a, b) { return a[0] - b[0]; });
 
         return res;
     }
-    
+
     function setupCategoriesForAxis(series, axis, datapoints) {
-        if (series[axis].options.mode != "categories")
+        if (series[axis].options.mode !== "categories") {
             return;
-        
+        }
+
         if (!series[axis].categories) {
             // parse options
             var c = {}, o = series[axis].options.categories || {};
             if ($.isArray(o)) {
-                for (var i = 0; i < o.length; ++i)
+                for (var i = 0; i < o.length; ++i) {
                     c[o[i]] = i;
+                }
+            } else {
+                for (var v in o) {
+                    c[v] = o[v];
+                }
             }
-            else {
-                for (var v in o)
-                    c[v] = o[v];
-            }
-            
+
             series[axis].categories = c;
         }
 
         // fix ticks
-        if (!series[axis].options.ticks)
+        if (!series[axis].options.ticks) {
             series[axis].options.ticks = categoriesTickGenerator;
+        }
 
         transformPointsOnAxis(datapoints, axis, series[axis].categories);
     }
-    
+
     function transformPointsOnAxis(datapoints, axis, categories) {
         // go through the points, transforming them
         var points = datapoints.points,
@@ -152,20 +162,22 @@
             index = getNextIndex(categories);
 
         for (var i = 0; i < points.length; i += ps) {
-            if (points[i] == null)
+            if (points[i] == null) {
                 continue;
-            
+            }
+
             for (var m = 0; m < ps; ++m) {
                 var val = points[i + m];
 
-                if (val == null || !format[m][formatColumn])
+                if (val == null || !format[m][formatColumn]) {
                     continue;
+                }
 
                 if (!(val in categories)) {
                     categories[val] = index;
                     ++index;
                 }
-                
+
                 points[i + m] = categories[val];
             }
         }
@@ -180,7 +192,7 @@
         plot.hooks.processRawData.push(processRawData);
         plot.hooks.processDatapoints.push(processDatapoints);
     }
-    
+
     $.plot.plugins.push({
         init: init,
         options: options,
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.categories.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.categories.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,1 +1,1 @@
-!function(r){function o(r,o,e,i){var s="categories"==o.xaxis.options.mode,n="categories"==o.yaxis.options.mode;if(s||n){var a=i.format;if(!a){var t=o;if((a=[]).push({x:!0,number:!0,required:!0}),a.push({y:!0,number:!0,required:!0}),t.bars.show||t.lines.show&&t.lines.fill){var u=!!(t.bars.show&&t.bars.zero||t.lines.show&&t.lines.zero);a.push({y:!0,number:!0,required:!1,defaultValue:0,autoscale:u}),t.bars.horizontal&&(delete a[a.length-1].y,a[a.length-1].x=!0)}i.format=a}for(var f=0;f<a.length;++f)a[f].x&&s&&(a[f].number=!1),a[f].y&&n&&(a[f].number=!1)}}function e(r){var o=-1;for(var e in r)r[e]>o&&(o=r[e]);return o+1}function i(r){var o=[];for(var e in r.categories){var i=r.categories[e];i>=r.min&&i<=r.max&&o.push([i,e])}return o.sort(function(r,o){return r[0]-o[0]}),o}function s(o,e,s){if("categories"==o[e].options.mode){if(!o[e].categories){var a={},t=o[e].options.categories||{};if(r.isArray(t))for(var u=0;u<t.length;++u)a[t[u]]=u;else for(var f in t)a[f]=t[f];o[e].categories=a}o[e].options.ticks||(o[e].options.ticks=i),n(s,e,o[e].categories)}}function n(r,o,i){for(var s=r.points,n=r.pointsize,a=r.format,t=o.charAt(0),u=e(i),f=0;f<s.length;f+=n)if(null!=s[f])for(var c=0;c<n;++c){var l=s[f+c];null!=l&&a[c][t]&&(l in i||(i[l]=u,++u),s[f+c]=i[l])}}function a(r,o,e){s(o,"xaxis",e),s(o,"yaxis",e)}r.plot.plugins.push({init:function(r){r.hooks.processRawData.push(o),r.hooks.processDatapoints.push(a)},options:{xaxis:{categories:null},yaxis:{categories:null}},name:"categories",version:"1.0"})}(jQuery);
+!function(e){function o(e,o,r,i){var n="categories"===o.xaxis.options.mode,s="categories"===o.yaxis.options.mode;if(n||s){var a=i.format;if(!a){var t=o;if((a=[]).push({x:!0,number:!0,required:!0,computeRange:!0}),a.push({y:!0,number:!0,required:!0,computeRange:!0}),t.bars.show||t.lines.show&&t.lines.fill){var u=!!(t.bars.show&&t.bars.zero||t.lines.show&&t.lines.zero);a.push({y:!0,number:!0,required:!1,defaultValue:0,computeRange:u}),t.bars.horizontal&&(delete a[a.length-1].y,a[a.length-1].x=!0)}i.format=a}for(var c=0;c<a.length;++c)a[c].x&&n&&(a[c].number=!1),a[c].y&&s&&(a[c].number=!1,a[c].computeRange=!1)}}function r(e){var o=-1;for(var r in e)e[r]>o&&(o=e[r]);return o+1}function i(e){var o=[];for(var r in e.categories){var i=e.categories[r];i>=e.min&&i<=e.max&&o.push([i,r])}return o.sort(function(e,o){return e[0]-o[0]}),o}function n(o,r,n){if("categories"===o[r].options.mode){if(!o[r].categories){var a={},t=o[r].options.categories||{};if(e.isArray(t))for(var u=0;u<t.length;++u)a[t[u]]=u;else for(var c in t)a[c]=t[c];o[r].categories=a}o[r].options.ticks||(o[r].options.ticks=i),s(n,r,o[r].categories)}}function s(e,o,i){for(var n=e.points,s=e.pointsize,a=e.format,t=o.charAt(0),u=r(i),c=0;c<n.length;c+=s)if(null!=n[c])for(var f=0;f<s;++f){var l=n[c+f];null!=l&&a[f][t]&&(l in i||(i[l]=u,++u),n[c+f]=i[l])}}function a(e,o,r){n(o,"xaxis",r),n(o,"yaxis",r)}e.plot.plugins.push({init:function(e){e.hooks.processRawData.push(o),e.hooks.processDatapoints.push(a)},options:{xaxis:{categories:null},yaxis:{categories:null}},name:"categories",version:"1.0"})}(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.composeImages.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,330 @@
+/** ## jquery.flot.composeImages.js
+
+This plugin is used to expose a function used to overlap several canvases and
+SVGs, for the purpose of creating a snaphot out of them.
+
+### When composeImages is used:
+When multiple canvases and SVGs have to be overlapped into a single image
+and their offset on the page, must be preserved.
+
+### Where can be used:
+In creating a downloadable snapshot of the plots, axes, cursors etc of a graph.
+
+### How it works:
+The entry point is composeImages function. It expects an array of objects,
+which should be either canvases or SVGs (or a mix). It does a prevalidation
+of them, by verifying if they will be usable or not, later in the flow.
+After selecting only usable sources, it passes them to getGenerateTempImg
+function, which generates temporary images out of them. This function
+expects that some of the passed sources (canvas or SVG) may still have
+problems being converted to an image and makes sure the promises system,
+used by composeImages function, moves forward. As an example, SVGs with
+missing information from header or with unsupported content, may lead to
+failure in generating the temporary image. Temporary images are required
+mostly on extracting content from SVGs, but this is also where the x/y
+offsets are extracted for each image which will be added. For SVGs in
+particular, their CSS rules have to be applied.
+After all temporary images are generated, they are overlapped using
+getExecuteImgComposition function. This is where the destination canvas
+is set to the proper dimensions. It is then output by composeImages.
+This function returns a promise, which can be used to wait for the whole
+composition process. It requires to be asynchronous, because this is how
+temporary images load their data.
+*/
+
+(function($) {
+    "use strict";
+    const GENERALFAILURECALLBACKERROR = -100; //simply a negative number
+    const SUCCESSFULIMAGEPREPARATION = 0;
+    const EMPTYARRAYOFIMAGESOURCES = -1;
+    const NEGATIVEIMAGESIZE = -2;
+    var pixelRatio = 1;
+    var browser = $.plot.browser;
+    var getPixelRatio = browser.getPixelRatio;
+
+    function composeImages(canvasOrSvgSources, destinationCanvas) {
+        var validCanvasOrSvgSources = canvasOrSvgSources.filter(isValidSource);
+        pixelRatio = getPixelRatio(destinationCanvas.getContext('2d'));
+
+        var allImgCompositionPromises = validCanvasOrSvgSources.map(function(validCanvasOrSvgSource) {
+            var tempImg = new Image();
+            var currentPromise = new Promise(getGenerateTempImg(tempImg, validCanvasOrSvgSource));
+            return currentPromise;
+        });
+
+        var lastPromise = Promise.all(allImgCompositionPromises).then(getExecuteImgComposition(destinationCanvas), failureCallback);
+        return lastPromise;
+    }
+
+    function isValidSource(canvasOrSvgSource) {
+        var isValidFromCanvas = true;
+        var isValidFromContent = true;
+        if ((canvasOrSvgSource === null) || (canvasOrSvgSource === undefined)) {
+            isValidFromContent = false;
+        } else {
+            if (canvasOrSvgSource.tagName === 'CANVAS') {
+                if ((canvasOrSvgSource.getBoundingClientRect().right === canvasOrSvgSource.getBoundingClientRect().left) ||
+                    (canvasOrSvgSource.getBoundingClientRect().bottom === canvasOrSvgSource.getBoundingClientRect().top)) {
+                    isValidFromCanvas = false;
+                }
+            }
+        }
+        return isValidFromContent && isValidFromCanvas && (window.getComputedStyle(canvasOrSvgSource).visibility === 'visible');
+    }
+
+    function getGenerateTempImg(tempImg, canvasOrSvgSource) {
+        tempImg.sourceDescription = '<info className="' + canvasOrSvgSource.className + '" tagName="' + canvasOrSvgSource.tagName + '" id="' + canvasOrSvgSource.id + '">';
+        tempImg.sourceComponent = canvasOrSvgSource;
+
+        return function doGenerateTempImg(successCallbackFunc, failureCallbackFunc) {
+            tempImg.onload = function(evt) {
+                tempImg.successfullyLoaded = true;
+                successCallbackFunc(tempImg);
+            };
+
+            tempImg.onabort = function(evt) {
+                tempImg.successfullyLoaded = false;
+                console.log('Can\'t generate temp image from ' + tempImg.sourceDescription + '. It is possible that it is missing some properties or its content is not supported by this browser. Source component:', tempImg.sourceComponent);
+                successCallbackFunc(tempImg); //call successCallback, to allow snapshot of all working images
+            };
+
+            tempImg.onerror = function(evt) {
+                tempImg.successfullyLoaded = false;
+                console.log('Can\'t generate temp image from ' + tempImg.sourceDescription + '. It is possible that it is missing some properties or its content is not supported by this browser. Source component:', tempImg.sourceComponent);
+                successCallbackFunc(tempImg); //call successCallback, to allow snapshot of all working images
+            };
+
+            generateTempImageFromCanvasOrSvg(canvasOrSvgSource, tempImg);
+        };
+    }
+
+    function getExecuteImgComposition(destinationCanvas) {
+        return function executeImgComposition(tempImgs) {
+            var compositionResult = copyImgsToCanvas(tempImgs, destinationCanvas);
+            return compositionResult;
+        };
+    }
+
+    function copyCanvasToImg(canvas, img) {
+        img.src = canvas.toDataURL('image/png');
+    }
+
+    function getCSSRules(document) {
+        var styleSheets = document.styleSheets,
+            rulesList = [];
+        for (var i = 0; i < styleSheets.length; i++) {
+            // CORS requests for style sheets throw and an exception on Chrome > 64
+            try {
+                // in Chrome, the external CSS files are empty when the page is directly loaded from disk
+                var rules = styleSheets[i].cssRules || [];
+                for (var j = 0; j < rules.length; j++) {
+                    var rule = rules[j];
+                    rulesList.push(rule.cssText);
+                }
+            } catch (e) {
+                console.log('Failed to get some css rules');
+            }
+        }
+        return rulesList;
+    }
+
+    function embedCSSRulesInSVG(rules, svg) {
+        var text = [
+            '<svg class="snapshot ' + svg.classList + '" width="' + svg.width.baseVal.value * pixelRatio + '" height="' + svg.height.baseVal.value * pixelRatio + '" viewBox="0 0 ' + svg.width.baseVal.value + ' ' + svg.height.baseVal.value + '" xmlns="http://www.w3.org/2000/svg">',
+            '<style>',
+            '/* <![CDATA[ */',
+            rules.join('\n'),
+            '/* ]]> */',
+            '</style>',
+            svg.innerHTML,
+            '</svg>'
+        ].join('\n');
+        return text;
+    }
+
+    function copySVGToImgMostBrowsers(svg, img) {
+        var rules = getCSSRules(document),
+            source = embedCSSRulesInSVG(rules, svg);
+
+        source = patchSVGSource(source);
+
+        var blob = new Blob([source], {type: "image/svg+xml;charset=utf-8"}),
+            domURL = self.URL || self.webkitURL || self,
+            url = domURL.createObjectURL(blob);
+        img.src = url;
+    }
+
+    function copySVGToImgSafari(svg, img) {
+        // Use this method to convert a string buffer array to a binary string.
+        // Do so by breaking up large strings into smaller substrings; this is necessary to avoid the
+        // "maximum call stack size exceeded" exception that can happen when calling 'String.fromCharCode.apply'
+        // with a very long array.
+        function buildBinaryString (arrayBuffer) {
+            var binaryString = "";
+            const utf8Array = new Uint8Array(arrayBuffer);
+            const blockSize = 16384;
+            for (var i = 0; i < utf8Array.length; i = i + blockSize) {
+                const binarySubString = String.fromCharCode.apply(null, utf8Array.subarray(i, i + blockSize));
+                binaryString = binaryString + binarySubString;
+            }
+            return binaryString;
+        };
+
+        var rules = getCSSRules(document),
+            source = embedCSSRulesInSVG(rules, svg),
+            data,
+            utf8BinaryString;
+
+        source = patchSVGSource(source);
+
+        // Encode the string as UTF-8 and convert it to a binary string. The UTF-8 encoding is required to
+        // capture unicode characters correctly.
+        utf8BinaryString = buildBinaryString(new (TextEncoder || TextEncoderLite)('utf-8').encode(source));
+
+        data = "data:image/svg+xml;base64," + btoa(utf8BinaryString);
+        img.src = data;
+    }
+
+    function patchSVGSource(svgSource) {
+        var source = '';
+        //add name spaces.
+        if (!svgSource.match(/^<svg[^>]+xmlns="http:\/\/www\.w3\.org\/2000\/svg"/)) {
+            source = svgSource.replace(/^<svg/, '<svg xmlns="http://www.w3.org/2000/svg"');
+        }
+        if (!svgSource.match(/^<svg[^>]+"http:\/\/www\.w3\.org\/1999\/xlink"/)) {
+            source = svgSource.replace(/^<svg/, '<svg xmlns:xlink="http://www.w3.org/1999/xlink"');
+        }
+
+        //add xml declaration
+        return '<?xml version="1.0" standalone="no"?>\r\n' + source;
+    }
+
+    function copySVGToImg(svg, img) {
+        if (browser.isSafari() || browser.isMobileSafari()) {
+            copySVGToImgSafari(svg, img);
+        } else {
+            copySVGToImgMostBrowsers(svg, img);
+        }
+    }
+
+    function adaptDestSizeToZoom(destinationCanvas, sources) {
+        function containsSVGs(source) {
+            return source.srcImgTagName === 'svg';
+        }
+
+        if (sources.find(containsSVGs) !== undefined) {
+            if (pixelRatio < 1) {
+                destinationCanvas.width = destinationCanvas.width * pixelRatio;
+                destinationCanvas.height = destinationCanvas.height * pixelRatio;
+            }
+        }
+    }
+
+    function prepareImagesToBeComposed(sources, destination) {
+        var result = SUCCESSFULIMAGEPREPARATION;
+        if (sources.length === 0) {
+            result = EMPTYARRAYOFIMAGESOURCES; //nothing to do if called without sources
+        } else {
+            var minX = sources[0].genLeft;
+            var minY = sources[0].genTop;
+            var maxX = sources[0].genRight;
+            var maxY = sources[0].genBottom;
+            var i = 0;
+
+            for (i = 1; i < sources.length; i++) {
+                if (minX > sources[i].genLeft) {
+                    minX = sources[i].genLeft;
+                }
+
+                if (minY > sources[i].genTop) {
+                    minY = sources[i].genTop;
+                }
+            }
+
+            for (i = 1; i < sources.length; i++) {
+                if (maxX < sources[i].genRight) {
+                    maxX = sources[i].genRight;
+                }
+
+                if (maxY < sources[i].genBottom) {
+                    maxY = sources[i].genBottom;
+                }
+            }
+
+            if ((maxX - minX <= 0) || (maxY - minY <= 0)) {
+                result = NEGATIVEIMAGESIZE; //this might occur on hidden images
+            } else {
+                destination.width = Math.round(maxX - minX);
+                destination.height = Math.round(maxY - minY);
+
+                for (i = 0; i < sources.length; i++) {
+                    sources[i].xCompOffset = sources[i].genLeft - minX;
+                    sources[i].yCompOffset = sources[i].genTop - minY;
+                }
+
+                adaptDestSizeToZoom(destination, sources);
+            }
+        }
+        return result;
+    }
+
+    function copyImgsToCanvas(sources, destination) {
+        var prepareImagesResult = prepareImagesToBeComposed(sources, destination);
+        if (prepareImagesResult === SUCCESSFULIMAGEPREPARATION) {
+            var destinationCtx = destination.getContext('2d');
+
+            for (var i = 0; i < sources.length; i++) {
+                if (sources[i].successfullyLoaded === true) {
+                    destinationCtx.drawImage(sources[i], sources[i].xCompOffset * pixelRatio, sources[i].yCompOffset * pixelRatio);
+                }
+            }
+        }
+        return prepareImagesResult;
+    }
+
+    function adnotateDestImgWithBoundingClientRect(srcCanvasOrSvg, destImg) {
+        destImg.genLeft = srcCanvasOrSvg.getBoundingClientRect().left;
+        destImg.genTop = srcCanvasOrSvg.getBoundingClientRect().top;
+
+        if (srcCanvasOrSvg.tagName === 'CANVAS') {
+            destImg.genRight = destImg.genLeft + srcCanvasOrSvg.width;
+            destImg.genBottom = destImg.genTop + srcCanvasOrSvg.height;
+        }
+
+        if (srcCanvasOrSvg.tagName === 'svg') {
+            destImg.genRight = srcCanvasOrSvg.getBoundingClientRect().right;
+            destImg.genBottom = srcCanvasOrSvg.getBoundingClientRect().bottom;
+        }
+    }
+
+    function generateTempImageFromCanvasOrSvg(srcCanvasOrSvg, destImg) {
+        if (srcCanvasOrSvg.tagName === 'CANVAS') {
+            copyCanvasToImg(srcCanvasOrSvg, destImg);
+        }
+
+        if (srcCanvasOrSvg.tagName === 'svg') {
+            copySVGToImg(srcCanvasOrSvg, destImg);
+        }
+
+        destImg.srcImgTagName = srcCanvasOrSvg.tagName;
+        adnotateDestImgWithBoundingClientRect(srcCanvasOrSvg, destImg);
+    }
+
+    function failureCallback() {
+        return GENERALFAILURECALLBACKERROR;
+    }
+
+    // used for testing
+    $.plot.composeImages = composeImages;
+
+    function init(plot) {
+        // used to extend the public API of the plot
+        plot.composeImages = composeImages;
+    }
+
+    $.plot.plugins.push({
+        init: init,
+        name: 'composeImages',
+        version: '1.0'
+    });
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.composeImages.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+Unexpected token: keyword (const)
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.crosshair.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.crosshair.js	Wed Oct 16 13:00:43 2019 +0200
@@ -5,11 +5,11 @@
 
 The plugin supports these options:
 
-	crosshair: {
-		mode: null or "x" or "y" or "xy"
-		color: color
-		lineWidth: number
-	}
+    crosshair: {
+        mode: null or "x" or "y" or "xy"
+        color: color
+        lineWidth: number
+    }
 
 Set the mode to one of "x", "y" or "xy". The "x" mode enables a vertical
 crosshair that lets you trace the values on the x axis, "y" enables a
@@ -39,19 +39,19 @@
 
     Example usage:
 
-	var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
-	$("#graph").bind( "plothover", function ( evt, position, item ) {
-		if ( item ) {
-			// Lock the crosshair to the data point being hovered
-			myFlot.lockCrosshair({
-				x: item.datapoint[ 0 ],
-				y: item.datapoint[ 1 ]
-			});
-		} else {
-			// Return normal crosshair operation
-			myFlot.unlockCrosshair();
-		}
-	});
+    var myFlot = $.plot( $("#graph"), ..., { crosshair: { mode: "x" } } };
+    $("#graph").bind( "plothover", function ( evt, position, item ) {
+        if ( item ) {
+            // Lock the crosshair to the data point being hovered
+            myFlot.lockCrosshair({
+                x: item.datapoint[ 0 ],
+                y: item.datapoint[ 1 ]
+            });
+        } else {
+            // Return normal crosshair operation
+            myFlot.unlockCrosshair();
+        }
+    });
 
   - unlockCrosshair()
 
@@ -66,63 +66,83 @@
             lineWidth: 1
         }
     };
-    
+
     function init(plot) {
         // position of crosshair in pixels
-        var crosshair = { x: -1, y: -1, locked: false };
+        var crosshair = {x: -1, y: -1, locked: false, highlighted: false};
 
         plot.setCrosshair = function setCrosshair(pos) {
-            if (!pos)
+            if (!pos) {
                 crosshair.x = -1;
-            else {
+            } else {
                 var o = plot.p2c(pos);
                 crosshair.x = Math.max(0, Math.min(o.left, plot.width()));
                 crosshair.y = Math.max(0, Math.min(o.top, plot.height()));
             }
-            
+
             plot.triggerRedrawOverlay();
         };
-        
+
         plot.clearCrosshair = plot.setCrosshair; // passes null for pos
-        
+
         plot.lockCrosshair = function lockCrosshair(pos) {
-            if (pos)
+            if (pos) {
                 plot.setCrosshair(pos);
+            }
+
             crosshair.locked = true;
         };
 
         plot.unlockCrosshair = function unlockCrosshair() {
             crosshair.locked = false;
+            crosshair.rect = null;
         };
 
         function onMouseOut(e) {
-            if (crosshair.locked)
+            if (crosshair.locked) {
                 return;
+            }
 
-            if (crosshair.x != -1) {
+            if (crosshair.x !== -1) {
                 crosshair.x = -1;
                 plot.triggerRedrawOverlay();
             }
         }
 
         function onMouseMove(e) {
-            if (crosshair.locked)
+            var offset = plot.offset();
+            if (crosshair.locked) {
+                var mouseX = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
+                var mouseY = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
+
+                if ((mouseX > crosshair.x - 4) && (mouseX < crosshair.x + 4) && (mouseY > crosshair.y - 4) && (mouseY < crosshair.y + 4)) {
+                    if (!crosshair.highlighted) {
+                        crosshair.highlighted = true;
+                        plot.triggerRedrawOverlay();
+                    }
+                } else {
+                    if (crosshair.highlighted) {
+                        crosshair.highlighted = false;
+                        plot.triggerRedrawOverlay();
+                    }
+                }
                 return;
-                
+            }
+
             if (plot.getSelection && plot.getSelection()) {
                 crosshair.x = -1; // hide the crosshair while selecting
                 return;
             }
-                
-            var offset = plot.offset();
+
             crosshair.x = Math.max(0, Math.min(e.pageX - offset.left, plot.width()));
             crosshair.y = Math.max(0, Math.min(e.pageY - offset.top, plot.height()));
             plot.triggerRedrawOverlay();
         }
-        
+
         plot.hooks.bindEvents.push(function (plot, eventHolder) {
-            if (!plot.getOptions().crosshair.mode)
+            if (!plot.getOptions().crosshair.mode) {
                 return;
+            }
 
             eventHolder.mouseout(onMouseOut);
             eventHolder.mousemove(onMouseMove);
@@ -130,15 +150,16 @@
 
         plot.hooks.drawOverlay.push(function (plot, ctx) {
             var c = plot.getOptions().crosshair;
-            if (!c.mode)
+            if (!c.mode) {
                 return;
+            }
 
             var plotOffset = plot.getPlotOffset();
-            
+
             ctx.save();
             ctx.translate(plotOffset.left, plotOffset.top);
 
-            if (crosshair.x != -1) {
+            if (crosshair.x !== -1) {
                 var adj = plot.getOptions().crosshair.lineWidth % 2 ? 0.5 : 0;
 
                 ctx.strokeStyle = c.color;
@@ -146,16 +167,21 @@
                 ctx.lineJoin = "round";
 
                 ctx.beginPath();
-                if (c.mode.indexOf("x") != -1) {
+                if (c.mode.indexOf("x") !== -1) {
                     var drawX = Math.floor(crosshair.x) + adj;
                     ctx.moveTo(drawX, 0);
                     ctx.lineTo(drawX, plot.height());
                 }
-                if (c.mode.indexOf("y") != -1) {
+                if (c.mode.indexOf("y") !== -1) {
                     var drawY = Math.floor(crosshair.y) + adj;
                     ctx.moveTo(0, drawY);
                     ctx.lineTo(plot.width(), drawY);
                 }
+                if (crosshair.locked) {
+                    if (crosshair.highlighted) ctx.fillStyle = 'orange';
+                    else ctx.fillStyle = c.color;
+                    ctx.fillRect(Math.floor(crosshair.x) + adj - 4, Math.floor(crosshair.y) + adj - 4, 8, 8);
+                }
                 ctx.stroke();
             }
             ctx.restore();
@@ -166,7 +192,7 @@
             eventHolder.unbind("mousemove", onMouseMove);
         });
     }
-    
+
     $.plot.plugins.push({
         init: init,
         options: options,
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.crosshair.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.crosshair.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,7 +1,1 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
-
-Copyright (c) 2007-2014 IOLA and Ole Laursen.
-Licensed under the MIT license.
-
-*/
-(function($){var options={crosshair:{mode:null,color:"rgba(170, 0, 0, 0.80)",lineWidth:1}};function init(plot){var crosshair={x:-1,y:-1,locked:false};plot.setCrosshair=function setCrosshair(pos){if(!pos)crosshair.x=-1;else{var o=plot.p2c(pos);crosshair.x=Math.max(0,Math.min(o.left,plot.width()));crosshair.y=Math.max(0,Math.min(o.top,plot.height()))}plot.triggerRedrawOverlay()};plot.clearCrosshair=plot.setCrosshair;plot.lockCrosshair=function lockCrosshair(pos){if(pos)plot.setCrosshair(pos);crosshair.locked=true};plot.unlockCrosshair=function unlockCrosshair(){crosshair.locked=false};function onMouseOut(e){if(crosshair.locked)return;if(crosshair.x!=-1){crosshair.x=-1;plot.triggerRedrawOverlay()}}function onMouseMove(e){if(crosshair.locked)return;if(plot.getSelection&&plot.getSelection()){crosshair.x=-1;return}var offset=plot.offset();crosshair.x=Math.max(0,Math.min(e.pageX-offset.left,plot.width()));crosshair.y=Math.max(0,Math.min(e.pageY-offset.top,plot.height()));plot.triggerRedrawOverlay()}plot.hooks.bindEvents.push(function(plot,eventHolder){if(!plot.getOptions().crosshair.mode)return;eventHolder.mouseout(onMouseOut);eventHolder.mousemove(onMouseMove)});plot.hooks.drawOverlay.push(function(plot,ctx){var c=plot.getOptions().crosshair;if(!c.mode)return;var plotOffset=plot.getPlotOffset();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);if(crosshair.x!=-1){var adj=plot.getOptions().crosshair.lineWidth%2?.5:0;ctx.strokeStyle=c.color;ctx.lineWidth=c.lineWidth;ctx.lineJoin="round";ctx.beginPath();if(c.mode.indexOf("x")!=-1){var drawX=Math.floor(crosshair.x)+adj;ctx.moveTo(drawX,0);ctx.lineTo(drawX,plot.height())}if(c.mode.indexOf("y")!=-1){var drawY=Math.floor(crosshair.y)+adj;ctx.moveTo(0,drawY);ctx.lineTo(plot.width(),drawY)}ctx.stroke()}ctx.restore()});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mouseout",onMouseOut);eventHolder.unbind("mousemove",onMouseMove)})}$.plot.plugins.push({init:init,options:options,name:"crosshair",version:"1.0"})})(jQuery);
\ No newline at end of file
+!function(e){jQuery.plot.plugins.push({init:function(e){function t(t){i.locked||-1!==i.x&&(i.x=-1,e.triggerRedrawOverlay())}function o(t){var o=e.offset();if(i.locked){var r=Math.max(0,Math.min(t.pageX-o.left,e.width())),h=Math.max(0,Math.min(t.pageY-o.top,e.height()));r>i.x-4&&r<i.x+4&&h>i.y-4&&h<i.y+4?i.highlighted||(i.highlighted=!0,e.triggerRedrawOverlay()):i.highlighted&&(i.highlighted=!1,e.triggerRedrawOverlay())}else e.getSelection&&e.getSelection()?i.x=-1:(i.x=Math.max(0,Math.min(t.pageX-o.left,e.width())),i.y=Math.max(0,Math.min(t.pageY-o.top,e.height())),e.triggerRedrawOverlay())}var i={x:-1,y:-1,locked:!1,highlighted:!1};e.setCrosshair=function(t){if(t){var o=e.p2c(t);i.x=Math.max(0,Math.min(o.left,e.width())),i.y=Math.max(0,Math.min(o.top,e.height()))}else i.x=-1;e.triggerRedrawOverlay()},e.clearCrosshair=e.setCrosshair,e.lockCrosshair=function(t){t&&e.setCrosshair(t),i.locked=!0},e.unlockCrosshair=function(){i.locked=!1,i.rect=null},e.hooks.bindEvents.push(function(e,i){e.getOptions().crosshair.mode&&(i.mouseout(t),i.mousemove(o))}),e.hooks.drawOverlay.push(function(e,t){var o=e.getOptions().crosshair;if(o.mode){var r=e.getPlotOffset();if(t.save(),t.translate(r.left,r.top),-1!==i.x){var h=e.getOptions().crosshair.lineWidth%2?.5:0;if(t.strokeStyle=o.color,t.lineWidth=o.lineWidth,t.lineJoin="round",t.beginPath(),-1!==o.mode.indexOf("x")){var a=Math.floor(i.x)+h;t.moveTo(a,0),t.lineTo(a,e.height())}if(-1!==o.mode.indexOf("y")){var l=Math.floor(i.y)+h;t.moveTo(0,l),t.lineTo(e.width(),l)}i.locked&&(i.highlighted?t.fillStyle="orange":t.fillStyle=o.color,t.fillRect(Math.floor(i.x)+h-4,Math.floor(i.y)+h-4,8,8)),t.stroke()}t.restore()}}),e.hooks.shutdown.push(function(e,i){i.unbind("mouseout",t),i.unbind("mousemove",o)})},options:{crosshair:{mode:null,color:"rgba(170, 0, 0, 0.80)",lineWidth:1}},name:"crosshair",version:"1.0"})}();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.drawSeries.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,663 @@
+/**
+## jquery.flot.drawSeries.js
+
+This plugin is used by flot for drawing lines, plots, bars or area.
+
+### Public methods
+*/
+
+(function($) {
+    "use strict";
+
+    function DrawSeries() {
+        function plotLine(datapoints, xoffset, yoffset, axisx, axisy, ctx, steps) {
+            var points = datapoints.points,
+                ps = datapoints.pointsize,
+                prevx = null,
+                prevy = null;
+            var x1 = 0.0,
+                y1 = 0.0,
+                x2 = 0.0,
+                y2 = 0.0,
+                mx = null,
+                my = null,
+                i = 0;
+
+            ctx.beginPath();
+            for (i = ps; i < points.length; i += ps) {
+                x1 = points[i - ps];
+                y1 = points[i - ps + 1];
+                x2 = points[i];
+                y2 = points[i + 1];
+
+                if (x1 === null || x2 === null) {
+                    mx = null;
+                    my = null;
+                    continue;
+                }
+
+                if (isNaN(x1) || isNaN(x2) || isNaN(y1) || isNaN(y2)) {
+                    prevx = null;
+                    prevy = null;
+                    continue;
+                }
+
+                if(steps){
+                    if (mx !== null && my !== null) {
+                        // if middle point exists, transfer p2 -> p1 and p1 -> mp
+                        x2 = x1;
+                        y2 = y1;
+                        x1 = mx;
+                        y1 = my;
+
+                        // 'remove' middle point
+                        mx = null;
+                        my = null;
+
+                        // subtract pointsize from i to have current point p1 handled again
+                        i -= ps;
+                    } else if (y1 !== y2 && x1 !== x2) {
+                        // create a middle point
+                        y2 = y1;
+                        mx = x2;
+                        my = y1;
+                    }
+                }
+
+                // clip with ymin
+                if (y1 <= y2 && y1 < axisy.min) {
+                    if (y2 < axisy.min) {
+                        // line segment is outside
+                        continue;
+                    }
+                    // compute new intersection point
+                    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;
+                }
+
+                // clip with ymax
+                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;
+                }
+
+                // clip with xmin
+                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;
+                }
+
+                // clip with xmax
+                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, fillTowards, ctx, steps) {
+            var points = datapoints.points,
+                ps = datapoints.pointsize,
+                bottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min,
+                i = 0,
+                ypos = 1,
+                areaOpen = false,
+                segmentStart = 0,
+                segmentEnd = 0,
+                mx = null,
+                my = null;
+
+            // we process each segment in two turns, first forward
+            // direction to sketch out top, then once we hit the
+            // end we go backwards to sketch the bottom
+            while (true) {
+                if (ps > 0 && i > points.length + ps) {
+                    break;
+                }
+
+                i += ps; // ps is negative if going backwards
+
+                var x1 = points[i - ps],
+                    y1 = points[i - ps + ypos],
+                    x2 = points[i],
+                    y2 = points[i + ypos];
+
+                if (ps === -2) {
+                    /* going backwards and no value for the bottom provided in the series*/
+                    y1 = y2 = bottom;
+                }
+
+                if (areaOpen) {
+                    if (ps > 0 && x1 != null && x2 == null) {
+                        // at turning point
+                        segmentEnd = i;
+                        ps = -ps;
+                        ypos = 2;
+                        continue;
+                    }
+
+                    if (ps < 0 && i === segmentStart + ps) {
+                        // done with the reverse sweep
+                        ctx.fill();
+                        areaOpen = false;
+                        ps = -ps;
+                        ypos = 1;
+                        i = segmentStart = segmentEnd + ps;
+                        continue;
+                    }
+                }
+
+                if (x1 == null || x2 == null) {
+                    mx = null;
+                    my = null;
+                    continue;
+                }
+
+                if(steps){
+                    if (mx !== null && my !== null) {
+                        // if middle point exists, transfer p2 -> p1 and p1 -> mp
+                        x2 = x1;
+                        y2 = y1;
+                        x1 = mx;
+                        y1 = my;
+
+                        // 'remove' middle point
+                        mx = null;
+                        my = null;
+
+                        // subtract pointsize from i to have current point p1 handled again
+                        i -= ps;
+                    } else if (y1 !== y2 && x1 !== x2) {
+                        // create a middle point
+                        y2 = y1;
+                        mx = x2;
+                        my = y1;
+                    }
+                }
+
+                // clip x values
+
+                // clip with xmin
+                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;
+                }
+
+                // clip with xmax
+                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) {
+                    // open area
+                    ctx.beginPath();
+                    ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
+                    areaOpen = true;
+                }
+
+                // now first check the case where both is outside
+                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;
+                }
+
+                // else it's a bit more complicated, there might
+                // be a flat maxed out rectangle first, then a
+                // triangular cutout or reverse; to find these
+                // keep track of the current x values
+                var x1old = x1,
+                    x2old = x2;
+
+                // clip the y values, without shortcutting, we
+                // go through all cases in turn
+
+                // clip with ymin
+                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;
+                }
+
+                // clip with ymax
+                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 the x value was changed we got a rectangle
+                // to fill
+                if (x1 !== x1old) {
+                    ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
+                    // it goes to (x1, y1), but we fill that below
+                }
+
+                // fill triangular section, this sometimes result
+                // in redundant points if (x1, y1) hasn't changed
+                // from previous line to, but we just ignore that
+                ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
+                ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+
+                // fill the other rectangle if it's there
+                if (x2 !== x2old) {
+                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
+                    ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
+                }
+            }
+        }
+
+        /**
+        - drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
+
+         This function is used for drawing lines or area fill.  In case the series has line decimation function
+         attached, before starting to draw, as an optimization the points will first be decimated.
+
+         The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
+         plotHeight are the corresponding parameters of flot used to determine the drawing surface.
+         The function getColorOrGradient is used to compute the fill style of lines and area.
+        */
+        function drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
+            ctx.save();
+            ctx.translate(plotOffset.left, plotOffset.top);
+            ctx.lineJoin = "round";
+
+            if (series.lines.dashes && ctx.setLineDash) {
+                ctx.setLineDash(series.lines.dashes);
+            }
+
+            var datapoints = {
+                format: series.datapoints.format,
+                points: series.datapoints.points,
+                pointsize: series.datapoints.pointsize
+            };
+
+            if (series.decimate) {
+                datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
+            }
+
+            var lw = series.lines.lineWidth;
+
+            ctx.lineWidth = lw;
+            ctx.strokeStyle = series.color;
+            var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight, getColorOrGradient);
+            if (fillStyle) {
+                ctx.fillStyle = fillStyle;
+                plotLineArea(datapoints, series.xaxis, series.yaxis, series.lines.fillTowards || 0, ctx, series.lines.steps);
+            }
+
+            if (lw > 0) {
+                plotLine(datapoints, 0, 0, series.xaxis, series.yaxis, ctx, series.lines.steps);
+            }
+
+            ctx.restore();
+        }
+
+        /**
+        - drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
+
+         This function is used for drawing points using a given symbol. In case the series has points decimation
+         function attached, before starting to draw, as an optimization the points will first be decimated.
+
+         The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
+         plotHeight are the corresponding parameters of flot used to determine the drawing surface.
+         The function drawSymbol is used to compute and draw the symbol chosen for the points.
+        */
+        function drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
+            function drawCircle(ctx, x, y, radius, shadow, fill) {
+                ctx.moveTo(x + radius, y);
+                ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
+            }
+            drawCircle.fill = true;
+            function plotPoints(datapoints, radius, fill, offset, shadow, axisx, axisy, drawSymbolFn) {
+                var points = datapoints.points,
+                    ps = datapoints.pointsize;
+
+                ctx.beginPath();
+                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;
+                    }
+
+                    x = axisx.p2c(x);
+                    y = axisy.p2c(y) + offset;
+
+                    drawSymbolFn(ctx, x, y, radius, shadow, fill);
+                }
+                if (drawSymbolFn.fill && !shadow) {
+                    ctx.fill();
+                }
+                ctx.stroke();
+            }
+
+            ctx.save();
+            ctx.translate(plotOffset.left, plotOffset.top);
+
+            var datapoints = {
+                format: series.datapoints.format,
+                points: series.datapoints.points,
+                pointsize: series.datapoints.pointsize
+            };
+
+            if (series.decimatePoints) {
+                datapoints.points = series.decimatePoints(series, series.xaxis.min, series.xaxis.max, plotWidth, series.yaxis.min, series.yaxis.max, plotHeight);
+            }
+
+            var lw = series.points.lineWidth,
+                radius = series.points.radius,
+                symbol = series.points.symbol,
+                drawSymbolFn;
+
+            if (symbol === 'circle') {
+                drawSymbolFn = drawCircle;
+            } else if (typeof symbol === 'string' && drawSymbol && drawSymbol[symbol]) {
+                drawSymbolFn = drawSymbol[symbol];
+            } else if (typeof drawSymbol === 'function') {
+                drawSymbolFn = drawSymbol;
+            }
+
+            // If the user sets the line width to 0, we change it to a very
+            // small value. A line width of 0 seems to force the default of 1.
+
+            if (lw === 0) {
+                lw = 0.0001;
+            }
+
+            ctx.lineWidth = lw;
+            ctx.fillStyle = getFillStyle(series.points, series.color, null, null, getColorOrGradient);
+            ctx.strokeStyle = series.color;
+            plotPoints(datapoints, radius,
+                true, 0, false,
+                series.xaxis, series.yaxis, drawSymbolFn);
+            ctx.restore();
+        }
+
+        function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
+            var left = x + barLeft,
+                right = x + barRight,
+                bottom = b, top = y,
+                drawLeft, drawRight, drawTop, drawBottom = false,
+                tmp;
+
+            drawLeft = drawRight = drawTop = true;
+
+            // in horizontal mode, we start the bar from the left
+            // instead of from the bottom so it appears to be
+            // horizontal rather than vertical
+            if (horizontal) {
+                drawBottom = drawRight = drawTop = true;
+                drawLeft = false;
+                left = b;
+                right = x;
+                top = y + barLeft;
+                bottom = y + barRight;
+
+                // account for negative bars
+                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;
+
+                // account for negative bars
+                if (top < bottom) {
+                    tmp = top;
+                    top = bottom;
+                    bottom = tmp;
+                    drawBottom = true;
+                    drawTop = false;
+                }
+            }
+
+            // clip
+            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);
+
+            // fill the bar
+            if (fillStyleCallback) {
+                c.fillStyle = fillStyleCallback(bottom, top);
+                c.fillRect(left, top, right - left, bottom - top)
+            }
+
+            // draw outline
+            if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
+                c.beginPath();
+
+                // FIXME: inline moveTo is buggy with excanvas
+                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();
+            }
+        }
+
+        /**
+        - drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient)
+
+         This function is used for drawing series represented as bars. In case the series has decimation
+         function attached, before starting to draw, as an optimization the points will first be decimated.
+
+         The series parameter contains the series to be drawn on ctx context. The plotOffset, plotWidth and
+         plotHeight are the corresponding parameters of flot used to determine the drawing surface.
+         The function getColorOrGradient is used to compute the fill style of bars.
+        */
+        function drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, drawSymbol, getColorOrGradient) {
+            function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
+                var points = datapoints.points,
+                    ps = datapoints.pointsize,
+                    fillTowards = series.bars.fillTowards || 0,
+                    calculatedBottom = fillTowards > axisy.min ? Math.min(axisy.max, fillTowards) : axisy.min;
+
+                for (var i = 0; i < points.length; i += ps) {
+                    if (points[i] == null) {
+                        continue;
+                    }
+
+                    // Use third point as bottom if pointsize is 3
+                    var bottom = ps === 3 ? points[i + 2] : calculatedBottom;
+                    drawBar(points[i], points[i + 1], bottom, barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
+                }
+            }
+
+            ctx.save();
+            ctx.translate(plotOffset.left, plotOffset.top);
+
+            var datapoints = {
+                format: series.datapoints.format,
+                points: series.datapoints.points,
+                pointsize: series.datapoints.pointsize
+            };
+
+            if (series.decimate) {
+                datapoints.points = series.decimate(series, series.xaxis.min, series.xaxis.max, plotWidth);
+            }
+
+            ctx.lineWidth = series.bars.lineWidth;
+            ctx.strokeStyle = series.color;
+
+            var barLeft;
+            var barWidth = series.bars.barWidth[0] || series.bars.barWidth;
+            switch (series.bars.align) {
+                case "left":
+                    barLeft = 0;
+                    break;
+                case "right":
+                    barLeft = -barWidth;
+                    break;
+                default:
+                    barLeft = -barWidth / 2;
+            }
+
+            var fillStyleCallback = series.bars.fill ? function(bottom, top) {
+                return getFillStyle(series.bars, series.color, bottom, top, getColorOrGradient);
+            } : null;
+
+            plotBars(datapoints, barLeft, barLeft + barWidth, fillStyleCallback, series.xaxis, series.yaxis);
+            ctx.restore();
+        }
+
+        function getFillStyle(filloptions, seriesColor, bottom, top, getColorOrGradient) {
+            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 : 0.4;
+            c.normalize();
+            return c.toString();
+        }
+
+        this.drawSeriesLines = drawSeriesLines;
+        this.drawSeriesPoints = drawSeriesPoints;
+        this.drawSeriesBars = drawSeriesBars;
+        this.drawBar = drawBar;
+    };
+
+    $.plot.drawSeries = new DrawSeries();
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.drawSeries.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(i){"use strict";i.plot.drawSeries=new function(){function n(i,n,a,e,l,t,o){var s=i.points,m=i.pointsize,r=null,f=null,c=0,x=0,p=0,u=0,d=null,h=null,v=0;for(t.beginPath(),v=m;v<s.length;v+=m)if(c=s[v-m],x=s[v-m+1],p=s[v],u=s[v+1],null!==c&&null!==p)if(isNaN(c)||isNaN(p)||isNaN(x)||isNaN(u))r=null,f=null;else{if(o&&(null!==d&&null!==h?(p=c,u=x,c=d,x=h,d=null,h=null,v-=m):x!==u&&c!==p&&(u=x,d=p,h=x)),x<=u&&x<l.min){if(u<l.min)continue;c=(l.min-x)/(u-x)*(p-c)+c,x=l.min}else if(u<=x&&u<l.min){if(x<l.min)continue;p=(l.min-x)/(u-x)*(p-c)+c,u=l.min}if(x>=u&&x>l.max){if(u>l.max)continue;c=(l.max-x)/(u-x)*(p-c)+c,x=l.max}else if(u>=x&&u>l.max){if(x>l.max)continue;p=(l.max-x)/(u-x)*(p-c)+c,u=l.max}if(c<=p&&c<e.min){if(p<e.min)continue;x=(e.min-c)/(p-c)*(u-x)+x,c=e.min}else if(p<=c&&p<e.min){if(c<e.min)continue;u=(e.min-c)/(p-c)*(u-x)+x,p=e.min}if(c>=p&&c>e.max){if(p>e.max)continue;x=(e.max-c)/(p-c)*(u-x)+x,c=e.max}else if(p>=c&&p>e.max){if(c>e.max)continue;u=(e.max-c)/(p-c)*(u-x)+x,p=e.max}c===r&&x===f||t.moveTo(e.p2c(c)+n,l.p2c(x)+a),r=p,f=u,t.lineTo(e.p2c(p)+n,l.p2c(u)+a)}else d=null,h=null;t.stroke()}function a(i,n,a,e,l,t){for(var o=i.points,s=i.pointsize,m=e>a.min?Math.min(a.max,e):a.min,r=0,f=1,c=!1,x=0,p=0,u=null,d=null;!(s>0&&r>o.length+s);){var h=o[(r+=s)-s],v=o[r-s+f],T=o[r],b=o[r+f];if(-2===s&&(v=b=m),c){if(s>0&&null!=h&&null==T){p=r,s=-s,f=2;continue}if(s<0&&r===x+s){l.fill(),c=!1,f=1,r=x=p+(s=-s);continue}}if(null!=h&&null!=T){if(t&&(null!==u&&null!==d?(T=h,b=v,h=u,v=d,u=null,d=null,r-=s):v!==b&&h!==T&&(b=v,u=T,d=v)),h<=T&&h<n.min){if(T<n.min)continue;v=(n.min-h)/(T-h)*(b-v)+v,h=n.min}else if(T<=h&&T<n.min){if(h<n.min)continue;b=(n.min-h)/(T-h)*(b-v)+v,T=n.min}if(h>=T&&h>n.max){if(T>n.max)continue;v=(n.max-h)/(T-h)*(b-v)+v,h=n.max}else if(T>=h&&T>n.max){if(h>n.max)continue;b=(n.max-h)/(T-h)*(b-v)+v,T=n.max}if(c||(l.beginPath(),l.moveTo(n.p2c(h),a.p2c(m)),c=!0),v>=a.max&&b>=a.max)l.lineTo(n.p2c(h),a.p2c(a.max)),l.lineTo(n.p2c(T),a.p2c(a.max));else if(v<=a.min&&b<=a.min)l.lineTo(n.p2c(h),a.p2c(a.min)),l.lineTo(n.p2c(T),a.p2c(a.min));else{var y=h,g=T;v<=b&&v<a.min&&b>=a.min?(h=(a.min-v)/(b-v)*(T-h)+h,v=a.min):b<=v&&b<a.min&&v>=a.min&&(T=(a.min-v)/(b-v)*(T-h)+h,b=a.min),v>=b&&v>a.max&&b<=a.max?(h=(a.max-v)/(b-v)*(T-h)+h,v=a.max):b>=v&&b>a.max&&v<=a.max&&(T=(a.max-v)/(b-v)*(T-h)+h,b=a.max),h!==y&&l.lineTo(n.p2c(y),a.p2c(v)),l.lineTo(n.p2c(h),a.p2c(v)),l.lineTo(n.p2c(T),a.p2c(b)),T!==g&&(l.lineTo(n.p2c(T),a.p2c(b)),l.lineTo(n.p2c(g),a.p2c(b)))}}else u=null,d=null}}function e(i,n,a,e,l,t,o,s,m,r,f){var c,x,p,u,d=i+e,h=i+l,v=a,T=n,b=!1;c=x=p=!0,r?(b=x=p=!0,c=!1,T=n+e,v=n+l,(h=i)<(d=a)&&(u=h,h=d,d=u,c=!0,x=!1)):(c=x=p=!0,b=!1,d=i+e,h=i+l,(T=n)<(v=a)&&(u=T,T=v,v=u,b=!0,p=!1)),h<o.min||d>o.max||T<s.min||v>s.max||(d<o.min&&(d=o.min,c=!1),h>o.max&&(h=o.max,x=!1),v<s.min&&(v=s.min,b=!1),T>s.max&&(T=s.max,p=!1),d=o.p2c(d),v=s.p2c(v),h=o.p2c(h),T=s.p2c(T),t&&(m.fillStyle=t(v,T),m.fillRect(d,T,h-d,v-T)),f>0&&(c||x||p||b)&&(m.beginPath(),m.moveTo(d,v),c?m.lineTo(d,T):m.moveTo(d,T),p?m.lineTo(h,T):m.moveTo(h,T),x?m.lineTo(h,v):m.moveTo(h,v),b?m.lineTo(d,v):m.moveTo(d,v),m.stroke()))}function l(n,a,e,l,t){var o=n.fill;if(!o)return null;if(n.fillColor)return t(n.fillColor,e,l,a);var s=i.color.parse(a);return s.a="number"==typeof o?o:.4,s.normalize(),s.toString()}this.drawSeriesLines=function(i,e,t,o,s,m,r){e.save(),e.translate(t.left,t.top),e.lineJoin="round",i.lines.dashes&&e.setLineDash&&e.setLineDash(i.lines.dashes);var f={format:i.datapoints.format,points:i.datapoints.points,pointsize:i.datapoints.pointsize};i.decimate&&(f.points=i.decimate(i,i.xaxis.min,i.xaxis.max,o,i.yaxis.min,i.yaxis.max,s));var c=i.lines.lineWidth;e.lineWidth=c,e.strokeStyle=i.color;var x=l(i.lines,i.color,0,s,r);x&&(e.fillStyle=x,a(f,i.xaxis,i.yaxis,i.lines.fillTowards||0,e,i.lines.steps)),c>0&&n(f,0,0,i.xaxis,i.yaxis,e,i.lines.steps),e.restore()},this.drawSeriesPoints=function(i,n,a,e,t,o,s){function m(i,n,a,e,l,t){i.moveTo(n+e,a),i.arc(n,a,e,0,l?Math.PI:2*Math.PI,!1)}m.fill=!0,n.save(),n.translate(a.left,a.top);var r={format:i.datapoints.format,points:i.datapoints.points,pointsize:i.datapoints.pointsize};i.decimatePoints&&(r.points=i.decimatePoints(i,i.xaxis.min,i.xaxis.max,e,i.yaxis.min,i.yaxis.max,t));var f,c=i.points.lineWidth,x=i.points.radius,p=i.points.symbol;"circle"===p?f=m:"string"==typeof p&&o&&o[p]?f=o[p]:"function"==typeof o&&(f=o),0===c&&(c=1e-4),n.lineWidth=c,n.fillStyle=l(i.points,i.color,null,null,s),n.strokeStyle=i.color,function(i,a,e,l,t,o,s,m){var r=i.points,f=i.pointsize;n.beginPath();for(var c=0;c<r.length;c+=f){var x=r[c],p=r[c+1];null==x||x<o.min||x>o.max||p<s.min||p>s.max||(x=o.p2c(x),p=s.p2c(p)+l,m(n,x,p,a,t,e))}m.fill&&!t&&n.fill(),n.stroke()}(r,x,!0,0,!1,i.xaxis,i.yaxis,f),n.restore()},this.drawSeriesBars=function(i,n,a,t,o,s,m){n.save(),n.translate(a.left,a.top);var r={format:i.datapoints.format,points:i.datapoints.points,pointsize:i.datapoints.pointsize};i.decimate&&(r.points=i.decimate(i,i.xaxis.min,i.xaxis.max,t)),n.lineWidth=i.bars.lineWidth,n.strokeStyle=i.color;var f,c=i.bars.barWidth[0]||i.bars.barWidth;switch(i.bars.align){case"left":f=0;break;case"right":f=-c;break;default:f=-c/2}!function(a,l,t,o,s,m){for(var r=a.points,f=a.pointsize,c=i.bars.fillTowards||0,x=c>m.min?Math.min(m.max,c):m.min,p=0;p<r.length;p+=f)if(null!=r[p]){var u=3===f?r[p+2]:x;e(r[p],r[p+1],u,l,t,o,s,m,n,i.bars.horizontal,i.bars.lineWidth)}}(r,f,f+c,i.bars.fill?function(n,a){return l(i.bars,i.color,n,a,m)}:null,i.xaxis,i.yaxis),n.restore()},this.drawBar=e}}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.errorbars.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.errorbars.js	Wed Oct 16 13:00:43 2019 +0200
@@ -15,44 +15,44 @@
 
 The plugin supports these options:
 
-	series: {
-		points: {
-			errorbars: "x" or "y" or "xy",
-			xerr: {
-				show: null/false or true,
-				asymmetric: null/false or true,
-				upperCap: null or "-" or function,
-				lowerCap: null or "-" or function,
-				color: null or color,
-				radius: null or number
-			},
-			yerr: { same options as xerr }
-		}
-	}
+    series: {
+        points: {
+            errorbars: "x" or "y" or "xy",
+            xerr: {
+                show: null/false or true,
+                asymmetric: null/false or true,
+                upperCap: null or "-" or function,
+                lowerCap: null or "-" or function,
+                color: null or color,
+                radius: null or number
+            },
+            yerr: { same options as xerr }
+        }
+    }
 
 Each data point array is expected to be of the type:
 
-	"x"  [ x, y, xerr ]
-	"y"  [ x, y, yerr ]
-	"xy" [ x, y, xerr, yerr ]
+    "x"  [ x, y, xerr ]
+    "y"  [ x, y, yerr ]
+    "xy" [ x, y, xerr, yerr ]
 
 Where xerr becomes xerr_lower,xerr_upper for the asymmetric error case, and
 equivalently for yerr. Eg., a datapoint for the "xy" case with symmetric
 error-bars on X and asymmetric on Y would be:
 
-	[ x, y, xerr, yerr_lower, yerr_upper ]
+    [ x, y, xerr, yerr_lower, yerr_upper ]
 
 By default no end caps are drawn. Setting upperCap and/or lowerCap to "-" will
 draw a small cap perpendicular to the error bar. They can also be set to a
 user-defined drawing function, with (ctx, x, y, radius) as parameters, as eg.
 
-	function drawSemiCircle( ctx, x, y, radius ) {
-		ctx.beginPath();
-		ctx.arc( x, y, radius, 0, Math.PI, false );
-		ctx.moveTo( x - radius, y );
-		ctx.lineTo( x + radius, y );
-		ctx.stroke();
-	}
+    function drawSemiCircle( ctx, x, y, radius ) {
+        ctx.beginPath();
+        ctx.arc( x, y, radius, 0, Math.PI, false );
+        ctx.moveTo( x - radius, y );
+        ctx.lineTo( x + radius, y );
+        ctx.stroke();
+    }
 
 Color and radius both default to the same ones of the points series if not
 set. The independent radius parameter on xerr/yerr is useful for the case when
@@ -67,15 +67,16 @@
         series: {
             points: {
                 errorbars: null, //should be 'x', 'y' or 'xy'
-                xerr: { err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null},
-                yerr: { err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}
+                xerr: {err: 'x', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null},
+                yerr: {err: 'y', show: null, asymmetric: null, upperCap: null, lowerCap: null, color: null, radius: null}
             }
         }
     };
 
-    function processRawData(plot, series, data, datapoints){
-        if (!series.points.errorbars)
+    function processRawData(plot, series, data, datapoints) {
+        if (!series.points.errorbars) {
             return;
+        }
 
         // x,y values
         var format = [
@@ -85,62 +86,74 @@
 
         var errors = series.points.errorbars;
         // error bars - first X then Y
-        if (errors == 'x' || errors == 'xy') {
+        if (errors === 'x' || errors === 'xy') {
             // lower / upper error
             if (series.points.xerr.asymmetric) {
                 format.push({ x: true, number: true, required: true });
                 format.push({ x: true, number: true, required: true });
-            } else
+            } else {
                 format.push({ x: true, number: true, required: true });
+            }
         }
-        if (errors == 'y' || errors == 'xy') {
+        if (errors === 'y' || errors === 'xy') {
             // lower / upper error
             if (series.points.yerr.asymmetric) {
                 format.push({ y: true, number: true, required: true });
                 format.push({ y: true, number: true, required: true });
-            } else
+            } else {
                 format.push({ y: true, number: true, required: true });
+            }
         }
         datapoints.format = format;
     }
 
-    function parseErrors(series, i){
-
+    function parseErrors(series, i) {
         var points = series.datapoints.points;
 
         // read errors from points array
         var exl = null,
-                exu = null,
-                eyl = null,
-                eyu = null;
+            exu = null,
+            eyl = null,
+            eyu = null;
         var xerr = series.points.xerr,
-                yerr = series.points.yerr;
+            yerr = series.points.yerr;
 
         var eb = series.points.errorbars;
         // error bars - first X
-        if (eb == 'x' || eb == 'xy') {
+        if (eb === 'x' || eb === 'xy') {
             if (xerr.asymmetric) {
                 exl = points[i + 2];
                 exu = points[i + 3];
-                if (eb == 'xy')
-                    if (yerr.asymmetric){
+                if (eb === 'xy') {
+                    if (yerr.asymmetric) {
                         eyl = points[i + 4];
                         eyu = points[i + 5];
-                    } else eyl = points[i + 4];
+                    } else {
+                        eyl = points[i + 4];
+                    }
+                }
             } else {
                 exl = points[i + 2];
-                if (eb == 'xy')
+                if (eb === 'xy') {
                     if (yerr.asymmetric) {
                         eyl = points[i + 3];
                         eyu = points[i + 4];
-                    } else eyl = points[i + 3];
+                    } else {
+                        eyl = points[i + 3];
+                    }
+                }
             }
         // only Y
-        } else if (eb == 'y')
-            if (yerr.asymmetric) {
-                eyl = points[i + 2];
-                eyu = points[i + 3];
-            } else eyl = points[i + 2];
+        } else {
+            if (eb === 'y') {
+                if (yerr.asymmetric) {
+                    eyl = points[i + 2];
+                    eyu = points[i + 3];
+                } else {
+                    eyl = points[i + 2];
+                }
+            }
+        }
 
         // symmetric errors?
         if (exu == null) exu = exl;
@@ -148,30 +161,30 @@
 
         var errRanges = [exl, exu, eyl, eyu];
         // nullify if not showing
-        if (!xerr.show){
+        if (!xerr.show) {
             errRanges[0] = null;
             errRanges[1] = null;
         }
-        if (!yerr.show){
+        if (!yerr.show) {
             errRanges[2] = null;
             errRanges[3] = null;
         }
         return errRanges;
     }
 
-    function drawSeriesErrors(plot, ctx, s){
-
+    function drawSeriesErrors(plot, ctx, s) {
         var points = s.datapoints.points,
-                ps = s.datapoints.pointsize,
-                ax = [s.xaxis, s.yaxis],
-                radius = s.points.radius,
-                err = [s.points.xerr, s.points.yerr];
+            ps = s.datapoints.pointsize,
+            ax = [s.xaxis, s.yaxis],
+            radius = s.points.radius,
+            err = [s.points.xerr, s.points.yerr],
+            tmp;
 
         //sanity check, in case some inverted axis hack is applied to flot
         var invertX = false;
         if (ax[0].p2c(ax[0].max) < ax[0].p2c(ax[0].min)) {
             invertX = true;
-            var tmp = err[0].lowerCap;
+            tmp = err[0].lowerCap;
             err[0].lowerCap = err[0].upperCap;
             err[0].upperCap = tmp;
         }
@@ -179,24 +192,21 @@
         var invertY = false;
         if (ax[1].p2c(ax[1].min) < ax[1].p2c(ax[1].max)) {
             invertY = true;
-            var tmp = err[1].lowerCap;
+            tmp = err[1].lowerCap;
             err[1].lowerCap = err[1].upperCap;
             err[1].upperCap = tmp;
         }
 
         for (var i = 0; i < s.datapoints.points.length; i += ps) {
-
             //parse
             var errRanges = parseErrors(s, i);
 
             //cycle xerr & yerr
-            for (var e = 0; e < err.length; e++){
-
+            for (var e = 0; e < err.length; e++) {
                 var minmax = [ax[e].min, ax[e].max];
 
                 //draw this error?
-                if (errRanges[e * err.length]){
-
+                if (errRanges[e * err.length]) {
                     //data coordinates
                     var x = points[i],
                         y = points[i + 1];
@@ -206,12 +216,17 @@
                         lower = [x, y][e] - errRanges[e * err.length];
 
                     //points outside of the canvas
-                    if (err[e].err == 'x')
-                        if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max)
+                    if (err[e].err === 'x') {
+                        if (y > ax[1].max || y < ax[1].min || upper < ax[0].min || lower > ax[0].max) {
                             continue;
-                    if (err[e].err == 'y')
-                        if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max)
+                        }
+                    }
+
+                    if (err[e].err === 'y') {
+                        if (x > ax[0].max || x < ax[0].min || upper < ax[1].min || lower > ax[1].max) {
                             continue;
+                        }
+                    }
 
                     // prevent errorbars getting out of the canvas
                     var drawUpper = true,
@@ -227,9 +242,9 @@
                     }
 
                     //sanity check, in case some inverted axis hack is applied to flot
-                    if ((err[e].err == 'x' && invertX) || (err[e].err == 'y' && invertY)) {
+                    if ((err[e].err === 'x' && invertX) || (err[e].err === 'y' && invertY)) {
                         //swap coordinates
-                        var tmp = lower;
+                        tmp = lower;
                         lower = upper;
                         upper = tmp;
                         tmp = drawLower;
@@ -241,9 +256,9 @@
                     }
 
                     // convert to pixels
-                    x = ax[0].p2c(x),
-                        y = ax[1].p2c(y),
-                        upper = ax[e].p2c(upper);
+                    x = ax[0].p2c(x);
+                    y = ax[1].p2c(y);
+                    upper = ax[e].p2c(upper);
                     lower = ax[e].p2c(lower);
                     minmax[0] = ax[e].p2c(minmax[0]);
                     minmax[1] = ax[e].p2c(minmax[1]);
@@ -257,13 +272,15 @@
                         var w = sw / 2;
                         ctx.lineWidth = w;
                         ctx.strokeStyle = "rgba(0,0,0,0.1)";
-                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w/2, minmax);
+                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w + w / 2, minmax);
 
                         ctx.strokeStyle = "rgba(0,0,0,0.2)";
-                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w/2, minmax);
+                        drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, w / 2, minmax);
                     }
 
-                    ctx.strokeStyle = err[e].color? err[e].color: s.color;
+                    ctx.strokeStyle = err[e].color
+                        ? err[e].color
+                        : s.color;
                     ctx.lineWidth = lw;
                     //draw it
                     drawError(ctx, err[e], x, y, upper, lower, drawUpper, drawLower, radius, 0, minmax);
@@ -272,69 +289,74 @@
         }
     }
 
-    function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){
-
+    function drawError(ctx, err, x, y, upper, lower, drawUpper, drawLower, radius, offset, minmax) {
         //shadow offset
         y += offset;
         upper += offset;
         lower += offset;
 
         // error bar - avoid plotting over circles
-        if (err.err == 'x'){
-            if (upper > x + radius) drawPath(ctx, [[upper,y],[Math.max(x + radius,minmax[0]),y]]);
+        if (err.err === 'x') {
+            if (upper > x + radius) drawPath(ctx, [[upper, y], [Math.max(x + radius, minmax[0]), y]]);
             else drawUpper = false;
-            if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius,minmax[1]),y],[lower,y]] );
+
+            if (lower < x - radius) drawPath(ctx, [[Math.min(x - radius, minmax[1]), y], [lower, y]]);
             else drawLower = false;
-        }
-        else {
-            if (upper < y - radius) drawPath(ctx, [[x,upper],[x,Math.min(y - radius,minmax[0])]] );
+        } else {
+            if (upper < y - radius) drawPath(ctx, [[x, upper], [x, Math.min(y - radius, minmax[0])]]);
             else drawUpper = false;
-            if (lower > y + radius) drawPath(ctx, [[x,Math.max(y + radius,minmax[1])],[x,lower]] );
+
+            if (lower > y + radius) drawPath(ctx, [[x, Math.max(y + radius, minmax[1])], [x, lower]]);
             else drawLower = false;
         }
 
         //internal radius value in errorbar, allows to plot radius 0 points and still keep proper sized caps
         //this is a way to get errorbars on lines without visible connecting dots
-        radius = err.radius != null? err.radius: radius;
+        radius = err.radius != null
+            ? err.radius
+            : radius;
 
         // upper cap
         if (drawUpper) {
-            if (err.upperCap == '-'){
-                if (err.err=='x') drawPath(ctx, [[upper,y - radius],[upper,y + radius]] );
-                else drawPath(ctx, [[x - radius,upper],[x + radius,upper]] );
-            } else if ($.isFunction(err.upperCap)){
-                if (err.err=='x') err.upperCap(ctx, upper, y, radius);
+            if (err.upperCap === '-') {
+                if (err.err === 'x') drawPath(ctx, [[upper, y - radius], [upper, y + radius]]);
+                else drawPath(ctx, [[x - radius, upper], [x + radius, upper]]);
+            } else if ($.isFunction(err.upperCap)) {
+                if (err.err === 'x') err.upperCap(ctx, upper, y, radius);
                 else err.upperCap(ctx, x, upper, radius);
             }
         }
         // lower cap
         if (drawLower) {
-            if (err.lowerCap == '-'){
-                if (err.err=='x') drawPath(ctx, [[lower,y - radius],[lower,y + radius]] );
-                else drawPath(ctx, [[x - radius,lower],[x + radius,lower]] );
-            } else if ($.isFunction(err.lowerCap)){
-                if (err.err=='x') err.lowerCap(ctx, lower, y, radius);
+            if (err.lowerCap === '-') {
+                if (err.err === 'x') drawPath(ctx, [[lower, y - radius], [lower, y + radius]]);
+                else drawPath(ctx, [[x - radius, lower], [x + radius, lower]]);
+            } else if ($.isFunction(err.lowerCap)) {
+                if (err.err === 'x') err.lowerCap(ctx, lower, y, radius);
                 else err.lowerCap(ctx, x, lower, radius);
             }
         }
     }
 
-    function drawPath(ctx, pts){
+    function drawPath(ctx, pts) {
         ctx.beginPath();
         ctx.moveTo(pts[0][0], pts[0][1]);
-        for (var p=1; p < pts.length; p++)
+        for (var p = 1; p < pts.length; p++) {
             ctx.lineTo(pts[p][0], pts[p][1]);
+        }
+
         ctx.stroke();
     }
 
-    function draw(plot, ctx){
+    function draw(plot, ctx) {
         var plotOffset = plot.getPlotOffset();
 
         ctx.save();
         ctx.translate(plotOffset.left, plotOffset.top);
         $.each(plot.getData(), function (i, s) {
-            if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show))
+            if (s.points.errorbars && (s.points.xerr.show || s.points.yerr.show)) {
                 drawSeriesErrors(plot, ctx, s);
+            }
         });
         ctx.restore();
     }
@@ -345,9 +367,9 @@
     }
 
     $.plot.plugins.push({
-                init: init,
-                options: options,
-                name: 'errorbars',
-                version: '1.0'
-            });
+        init: init,
+        options: options,
+        name: 'errorbars',
+        version: '1.0'
+    });
 })(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.errorbars.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.errorbars.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,7 +1,1 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
-
-Copyright (c) 2007-2014 IOLA and Ole Laursen.
-Licensed under the MIT license.
-
-*/
-(function($){var options={series:{points:{errorbars:null,xerr:{err:"x",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null},yerr:{err:"y",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null}}}};function processRawData(plot,series,data,datapoints){if(!series.points.errorbars)return;var format=[{x:true,number:true,required:true},{y:true,number:true,required:true}];var errors=series.points.errorbars;if(errors=="x"||errors=="xy"){if(series.points.xerr.asymmetric){format.push({x:true,number:true,required:true});format.push({x:true,number:true,required:true})}else format.push({x:true,number:true,required:true})}if(errors=="y"||errors=="xy"){if(series.points.yerr.asymmetric){format.push({y:true,number:true,required:true});format.push({y:true,number:true,required:true})}else format.push({y:true,number:true,required:true})}datapoints.format=format}function parseErrors(series,i){var points=series.datapoints.points;var exl=null,exu=null,eyl=null,eyu=null;var xerr=series.points.xerr,yerr=series.points.yerr;var eb=series.points.errorbars;if(eb=="x"||eb=="xy"){if(xerr.asymmetric){exl=points[i+2];exu=points[i+3];if(eb=="xy")if(yerr.asymmetric){eyl=points[i+4];eyu=points[i+5]}else eyl=points[i+4]}else{exl=points[i+2];if(eb=="xy")if(yerr.asymmetric){eyl=points[i+3];eyu=points[i+4]}else eyl=points[i+3]}}else if(eb=="y")if(yerr.asymmetric){eyl=points[i+2];eyu=points[i+3]}else eyl=points[i+2];if(exu==null)exu=exl;if(eyu==null)eyu=eyl;var errRanges=[exl,exu,eyl,eyu];if(!xerr.show){errRanges[0]=null;errRanges[1]=null}if(!yerr.show){errRanges[2]=null;errRanges[3]=null}return errRanges}function drawSeriesErrors(plot,ctx,s){var points=s.datapoints.points,ps=s.datapoints.pointsize,ax=[s.xaxis,s.yaxis],radius=s.points.radius,err=[s.points.xerr,s.points.yerr];var invertX=false;if(ax[0].p2c(ax[0].max)<ax[0].p2c(ax[0].min)){invertX=true;var tmp=err[0].lowerCap;err[0].lowerCap=err[0].upperCap;err[0].upperCap=tmp}var invertY=false;if(ax[1].p2c(ax[1].min)<ax[1].p2c(ax[1].max)){invertY=true;var tmp=err[1].lowerCap;err[1].lowerCap=err[1].upperCap;err[1].upperCap=tmp}for(var i=0;i<s.datapoints.points.length;i+=ps){var errRanges=parseErrors(s,i);for(var e=0;e<err.length;e++){var minmax=[ax[e].min,ax[e].max];if(errRanges[e*err.length]){var x=points[i],y=points[i+1];var upper=[x,y][e]+errRanges[e*err.length+1],lower=[x,y][e]-errRanges[e*err.length];if(err[e].err=="x")if(y>ax[1].max||y<ax[1].min||upper<ax[0].min||lower>ax[0].max)continue;if(err[e].err=="y")if(x>ax[0].max||x<ax[0].min||upper<ax[1].min||lower>ax[1].max)continue;var drawUpper=true,drawLower=true;if(upper>minmax[1]){drawUpper=false;upper=minmax[1]}if(lower<minmax[0]){drawLower=false;lower=minmax[0]}if(err[e].err=="x"&&invertX||err[e].err=="y"&&invertY){var tmp=lower;lower=upper;upper=tmp;tmp=drawLower;drawLower=drawUpper;drawUpper=tmp;tmp=minmax[0];minmax[0]=minmax[1];minmax[1]=tmp}x=ax[0].p2c(x),y=ax[1].p2c(y),upper=ax[e].p2c(upper);lower=ax[e].p2c(lower);minmax[0]=ax[e].p2c(minmax[0]);minmax[1]=ax[e].p2c(minmax[1]);var lw=err[e].lineWidth?err[e].lineWidth:s.points.lineWidth,sw=s.points.shadowSize!=null?s.points.shadowSize:s.shadowSize;if(lw>0&&sw>0){var w=sw/2;ctx.lineWidth=w;ctx.strokeStyle="rgba(0,0,0,0.1)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w+w/2,minmax);ctx.strokeStyle="rgba(0,0,0,0.2)";drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,w/2,minmax)}ctx.strokeStyle=err[e].color?err[e].color:s.color;ctx.lineWidth=lw;drawError(ctx,err[e],x,y,upper,lower,drawUpper,drawLower,radius,0,minmax)}}}}function drawError(ctx,err,x,y,upper,lower,drawUpper,drawLower,radius,offset,minmax){y+=offset;upper+=offset;lower+=offset;if(err.err=="x"){if(upper>x+radius)drawPath(ctx,[[upper,y],[Math.max(x+radius,minmax[0]),y]]);else drawUpper=false;if(lower<x-radius)drawPath(ctx,[[Math.min(x-radius,minmax[1]),y],[lower,y]]);else drawLower=false}else{if(upper<y-radius)drawPath(ctx,[[x,upper],[x,Math.min(y-radius,minmax[0])]]);else drawUpper=false;if(lower>y+radius)drawPath(ctx,[[x,Math.max(y+radius,minmax[1])],[x,lower]]);else drawLower=false}radius=err.radius!=null?err.radius:radius;if(drawUpper){if(err.upperCap=="-"){if(err.err=="x")drawPath(ctx,[[upper,y-radius],[upper,y+radius]]);else drawPath(ctx,[[x-radius,upper],[x+radius,upper]])}else if($.isFunction(err.upperCap)){if(err.err=="x")err.upperCap(ctx,upper,y,radius);else err.upperCap(ctx,x,upper,radius)}}if(drawLower){if(err.lowerCap=="-"){if(err.err=="x")drawPath(ctx,[[lower,y-radius],[lower,y+radius]]);else drawPath(ctx,[[x-radius,lower],[x+radius,lower]])}else if($.isFunction(err.lowerCap)){if(err.err=="x")err.lowerCap(ctx,lower,y,radius);else err.lowerCap(ctx,x,lower,radius)}}}function drawPath(ctx,pts){ctx.beginPath();ctx.moveTo(pts[0][0],pts[0][1]);for(var p=1;p<pts.length;p++)ctx.lineTo(pts[p][0],pts[p][1]);ctx.stroke()}function draw(plot,ctx){var plotOffset=plot.getPlotOffset();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);$.each(plot.getData(),function(i,s){if(s.points.errorbars&&(s.points.xerr.show||s.points.yerr.show))drawSeriesErrors(plot,ctx,s)});ctx.restore()}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.draw.push(draw)}$.plot.plugins.push({init:init,options:options,name:"errorbars",version:"1.0"})})(jQuery);
\ No newline at end of file
+!function(r){function e(r,e,n,i){if(e.points.errorbars){var o=[{x:!0,number:!0,required:!0},{y:!0,number:!0,required:!0}],a=e.points.errorbars;"x"!==a&&"xy"!==a||(e.points.xerr.asymmetric?(o.push({x:!0,number:!0,required:!0}),o.push({x:!0,number:!0,required:!0})):o.push({x:!0,number:!0,required:!0})),"y"!==a&&"xy"!==a||(e.points.yerr.asymmetric?(o.push({y:!0,number:!0,required:!0}),o.push({y:!0,number:!0,required:!0})):o.push({y:!0,number:!0,required:!0})),i.format=o}}function n(r,e){var n=r.datapoints.points,i=null,o=null,a=null,p=null,t=r.points.xerr,l=r.points.yerr,s=r.points.errorbars;"x"===s||"xy"===s?t.asymmetric?(i=n[e+2],o=n[e+3],"xy"===s&&(l.asymmetric?(a=n[e+4],p=n[e+5]):a=n[e+4])):(i=n[e+2],"xy"===s&&(l.asymmetric?(a=n[e+3],p=n[e+4]):a=n[e+3])):"y"===s&&(l.asymmetric?(a=n[e+2],p=n[e+3]):a=n[e+2]),null==o&&(o=i),null==p&&(p=a);var u=[i,o,a,p];return t.show||(u[0]=null,u[1]=null),l.show||(u[2]=null,u[3]=null),u}function i(r,e,i){var a,p=i.datapoints.points,t=i.datapoints.pointsize,l=[i.xaxis,i.yaxis],s=i.points.radius,u=[i.points.xerr,i.points.yerr],m=!1;l[0].p2c(l[0].max)<l[0].p2c(l[0].min)&&(m=!0,a=u[0].lowerCap,u[0].lowerCap=u[0].upperCap,u[0].upperCap=a);var c=!1;l[1].p2c(l[1].min)<l[1].p2c(l[1].max)&&(c=!0,a=u[1].lowerCap,u[1].lowerCap=u[1].upperCap,u[1].upperCap=a);for(var h=0;h<i.datapoints.points.length;h+=t)for(var x=n(i,h),y=0;y<u.length;y++){var d=[l[y].min,l[y].max];if(x[y*u.length]){var f=p[h],w=p[h+1],C=[f,w][y]+x[y*u.length+1],b=[f,w][y]-x[y*u.length];if("x"===u[y].err&&(w>l[1].max||w<l[1].min||C<l[0].min||b>l[0].max))continue;if("y"===u[y].err&&(f>l[0].max||f<l[0].min||C<l[1].min||b>l[1].max))continue;var v=!0,g=!0;C>d[1]&&(v=!1,C=d[1]),b<d[0]&&(g=!1,b=d[0]),("x"===u[y].err&&m||"y"===u[y].err&&c)&&(a=b,b=C,C=a,a=g,g=v,v=a,a=d[0],d[0]=d[1],d[1]=a),f=l[0].p2c(f),w=l[1].p2c(w),C=l[y].p2c(C),b=l[y].p2c(b),d[0]=l[y].p2c(d[0]),d[1]=l[y].p2c(d[1]);var q=u[y].lineWidth?u[y].lineWidth:i.points.lineWidth,k=null!=i.points.shadowSize?i.points.shadowSize:i.shadowSize;if(q>0&&k>0){var S=k/2;e.lineWidth=S,e.strokeStyle="rgba(0,0,0,0.1)",o(e,u[y],f,w,C,b,v,g,s,S+S/2,d),e.strokeStyle="rgba(0,0,0,0.2)",o(e,u[y],f,w,C,b,v,g,s,S/2,d)}e.strokeStyle=u[y].color?u[y].color:i.color,e.lineWidth=q,o(e,u[y],f,w,C,b,v,g,s,0,d)}}}function o(e,n,i,o,p,t,l,s,u,m,c){o+=m,p+=m,t+=m,"x"===n.err?(p>i+u?a(e,[[p,o],[Math.max(i+u,c[0]),o]]):l=!1,t<i-u?a(e,[[Math.min(i-u,c[1]),o],[t,o]]):s=!1):(p<o-u?a(e,[[i,p],[i,Math.min(o-u,c[0])]]):l=!1,t>o+u?a(e,[[i,Math.max(o+u,c[1])],[i,t]]):s=!1),u=null!=n.radius?n.radius:u,l&&("-"===n.upperCap?"x"===n.err?a(e,[[p,o-u],[p,o+u]]):a(e,[[i-u,p],[i+u,p]]):r.isFunction(n.upperCap)&&("x"===n.err?n.upperCap(e,p,o,u):n.upperCap(e,i,p,u))),s&&("-"===n.lowerCap?"x"===n.err?a(e,[[t,o-u],[t,o+u]]):a(e,[[i-u,t],[i+u,t]]):r.isFunction(n.lowerCap)&&("x"===n.err?n.lowerCap(e,t,o,u):n.lowerCap(e,i,t,u)))}function a(r,e){r.beginPath(),r.moveTo(e[0][0],e[0][1]);for(var n=1;n<e.length;n++)r.lineTo(e[n][0],e[n][1]);r.stroke()}function p(e,n){var o=e.getPlotOffset();n.save(),n.translate(o.left,o.top),r.each(e.getData(),function(r,o){o.points.errorbars&&(o.points.xerr.show||o.points.yerr.show)&&i(e,n,o)}),n.restore()}r.plot.plugins.push({init:function(r){r.hooks.processRawData.push(e),r.hooks.draw.push(p)},options:{series:{points:{errorbars:null,xerr:{err:"x",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null},yerr:{err:"y",show:null,asymmetric:null,upperCap:null,lowerCap:null,color:null,radius:null}}}},name:"errorbars",version:"1.0"})}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.fillbetween.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.fillbetween.js	Wed Oct 16 13:00:43 2019 +0200
@@ -10,12 +10,12 @@
 
 In order to name the other series, you need to give it an id, like this:
 
-	var dataset = [
-		{ data: [ ... ], id: "foo" } ,         // use default bottom
-		{ data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
-	];
+    var dataset = [
+        { data: [ ... ], id: "foo" } ,         // use default bottom
+        { data: [ ... ], fillBetween: "foo" }, // use first dataset as bottom
+    ];
 
-	$.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
+    $.plot($("#placeholder"), dataset, { lines: { show: true, fill: true }});
 
 As a convenience, if the id given is a number that doesn't appear as an id in
 the series, it is interpreted as the index in the array instead (so fillBetween:
@@ -29,198 +29,226 @@
 
 */
 
-(function ( $ ) {
-
-	var options = {
-		series: {
-			fillBetween: null	// or number
-		}
-	};
-
-	function init( plot ) {
+(function ($) {
+    var options = {
+        series: {
+            fillBetween: null    // or number
+        }
+    };
 
-		function findBottomSeries( s, allseries ) {
-
-			var i;
+    function init(plot) {
+        function findBottomSeries(s, allseries) {
+            var i;
 
-			for ( i = 0; i < allseries.length; ++i ) {
-				if ( allseries[ i ].id === s.fillBetween ) {
-					return allseries[ i ];
-				}
-			}
+            for (i = 0; i < allseries.length; ++i) {
+                if (allseries[ i ].id === s.fillBetween) {
+                    return allseries[ i ];
+                }
+            }
 
-			if ( typeof s.fillBetween === "number" ) {
-				if ( s.fillBetween < 0 || s.fillBetween >= allseries.length ) {
-					return null;
-				}
-				return allseries[ s.fillBetween ];
-			}
+            if (typeof s.fillBetween === "number") {
+                if (s.fillBetween < 0 || s.fillBetween >= allseries.length) {
+                    return null;
+                }
+                return allseries[ s.fillBetween ];
+            }
 
-			return null;
-		}
+            return null;
+        }
 
-		function computeFillBottoms( plot, s, datapoints ) {
-
-			if ( s.fillBetween == null ) {
-				return;
-			}
+        function computeFormat(plot, s, data, datapoints) {
+            if (s.fillBetween == null) {
+                return;
+            }
+            
+            format = datapoints.format;
+            var plotHasId = function(id) {
+                var plotData = plot.getData();
+                for (i = 0; i < plotData.length; i++) {
+                    if (plotData[i].id === id) {
+                        return true;
+                    }
+                }
 
-			var other = findBottomSeries( s, plot.getData() );
+                return false;
+            }
 
-			if ( !other ) {
-				return;
-			}
+            if (!format) {
+                format = [];
 
-			var ps = datapoints.pointsize,
-				points = datapoints.points,
-				otherps = other.datapoints.pointsize,
-				otherpoints = other.datapoints.points,
-				newpoints = [],
-				px, py, intery, qx, qy, bottom,
-				withlines = s.lines.show,
-				withbottom = ps > 2 && datapoints.format[2].y,
-				withsteps = withlines && s.lines.steps,
-				fromgap = true,
-				i = 0,
-				j = 0,
-				l, m;
+                format.push({ 
+                    x: true, 
+                    number: true, 
+                    computeRange: s.xaxis.options.autoScale !== 'none',
+                    required: true 
+                });
+                format.push({ 
+                    y: true, 
+                    number: true, 
+                    computeRange: s.yaxis.options.autoScale !== 'none',
+                    required: true 
+                });
 
-			while ( true ) {
-
-				if ( i >= points.length ) {
-					break;
-				}
+                if (s.fillBetween !== undefined && s.fillBetween !== '' && plotHasId(s.fillBetween) && s.fillBetween !== s.id) {
+                    format.push({
+                        x: false,
+                        y: true,
+                        number: true,
+                        required: false,
+                        computeRange: s.yaxis.options.autoScale !== 'none',
+                        defaultValue: 0
+                    });
+                }
 
-				l = newpoints.length;
-
-				if ( points[ i ] == null ) {
-
-					// copy gaps
+                datapoints.format = format;
+            }
+        }
 
-					for ( m = 0; m < ps; ++m ) {
-						newpoints.push( points[ i + m ] );
-					}
+        function computeFillBottoms(plot, s, datapoints) {
+            if (s.fillBetween == null) {
+                return;
+            }
 
-					i += ps;
+            var other = findBottomSeries(s, plot.getData());
 
-				} else if ( j >= otherpoints.length ) {
-
-					// for lines, we can't use the rest of the points
+            if (!other) {
+                return;
+            }
 
-					if ( !withlines ) {
-						for ( m = 0; m < ps; ++m ) {
-							newpoints.push( points[ i + m ] );
-						}
-					}
-
-					i += ps;
+            var ps = datapoints.pointsize,
+                points = datapoints.points,
+                otherps = other.datapoints.pointsize,
+                otherpoints = other.datapoints.points,
+                newpoints = [],
+                px, py, intery, qx, qy, bottom,
+                withlines = s.lines.show,
+                withbottom = ps > 2 && datapoints.format[2].y,
+                withsteps = withlines && s.lines.steps,
+                fromgap = true,
+                i = 0,
+                j = 0,
+                l, m;
 
-				} else if ( otherpoints[ j ] == null ) {
-
-					// oops, got a gap
+            while (true) {
+                if (i >= points.length) {
+                    break;
+                }
 
-					for ( m = 0; m < ps; ++m ) {
-						newpoints.push( null );
-					}
-
-					fromgap = true;
-					j += otherps;
+                l = newpoints.length;
 
-				} else {
-
-					// cases where we actually got two points
-
-					px = points[ i ];
-					py = points[ i + 1 ];
-					qx = otherpoints[ j ];
-					qy = otherpoints[ j + 1 ];
-					bottom = 0;
-
-					if ( px === qx ) {
+                if (points[ i ] == null) {
+                    // copy gaps
+                    for (m = 0; m < ps; ++m) {
+                        newpoints.push(points[ i + m ]);
+                    }
 
-						for ( m = 0; m < ps; ++m ) {
-							newpoints.push( points[ i + m ] );
-						}
+                    i += ps;
+                } else if (j >= otherpoints.length) {
+                    // for lines, we can't use the rest of the points
+                    if (!withlines) {
+                        for (m = 0; m < ps; ++m) {
+                            newpoints.push(points[ i + m ]);
+                        }
+                    }
 
-						//newpoints[ l + 1 ] += qy;
-						bottom = qy;
-
-						i += ps;
-						j += otherps;
-
-					} else if ( px > qx ) {
+                    i += ps;
+                } else if (otherpoints[ j ] == null) {
+                    // oops, got a gap
+                    for (m = 0; m < ps; ++m) {
+                        newpoints.push(null);
+                    }
 
-						// we got past point below, might need to
-						// insert interpolated extra point
+                    fromgap = true;
+                    j += otherps;
+                } else {
+                    // cases where we actually got two points
+                    px = points[ i ];
+                    py = points[ i + 1 ];
+                    qx = otherpoints[ j ];
+                    qy = otherpoints[ j + 1 ];
+                    bottom = 0;
+
+                    if (px === qx) {
+                        for (m = 0; m < ps; ++m) {
+                            newpoints.push(points[ i + m ]);
+                        }
 
-						if ( withlines && i > 0 && points[ i - ps ] != null ) {
-							intery = py + ( points[ i - ps + 1 ] - py ) * ( qx - px ) / ( points[ i - ps ] - px );
-							newpoints.push( qx );
-							newpoints.push( intery );
-							for ( m = 2; m < ps; ++m ) {
-								newpoints.push( points[ i + m ] );
-							}
-							bottom = qy;
-						}
+                        //newpoints[ l + 1 ] += qy;
+                        bottom = qy;
+
+                        i += ps;
+                        j += otherps;
+                    } else if (px > qx) {
+                        // we got past point below, might need to
+                        // insert interpolated extra point
 
-						j += otherps;
-
-					} else { // px < qx
-
-						// if we come from a gap, we just skip this point
-
-						if ( fromgap && withlines ) {
-							i += ps;
-							continue;
-						}
+                        if (withlines && i > 0 && points[ i - ps ] != null) {
+                            intery = py + (points[ i - ps + 1 ] - py) * (qx - px) / (points[ i - ps ] - px);
+                            newpoints.push(qx);
+                            newpoints.push(intery);
+                            for (m = 2; m < ps; ++m) {
+                                newpoints.push(points[ i + m ]);
+                            }
+                            bottom = qy;
+                        }
 
-						for ( m = 0; m < ps; ++m ) {
-							newpoints.push( points[ i + m ] );
-						}
+                        j += otherps;
+                    } else {
+                        // px < qx
+                        // if we come from a gap, we just skip this point
 
-						// we might be able to interpolate a point below,
-						// this can give us a better y
+                        if (fromgap && withlines) {
+                            i += ps;
+                            continue;
+                        }
 
-						if ( withlines && j > 0 && otherpoints[ j - otherps ] != null ) {
-							bottom = qy + ( otherpoints[ j - otherps + 1 ] - qy ) * ( px - qx ) / ( otherpoints[ j - otherps ] - qx );
-						}
+                        for (m = 0; m < ps; ++m) {
+                            newpoints.push(points[ i + m ]);
+                        }
 
-						//newpoints[l + 1] += bottom;
+                        // we might be able to interpolate a point below,
+                        // this can give us a better y
 
-						i += ps;
-					}
-
-					fromgap = false;
+                        if (withlines && j > 0 && otherpoints[ j - otherps ] != null) {
+                            bottom = qy + (otherpoints[ j - otherps + 1 ] - qy) * (px - qx) / (otherpoints[ j - otherps ] - qx);
+                        }
 
-					if ( l !== newpoints.length && withbottom ) {
-						newpoints[ l + 2 ] = bottom;
-					}
-				}
+                        //newpoints[l + 1] += bottom;
 
-				// maintain the line steps invariant
+                        i += ps;
+                    }
+
+                    fromgap = false;
 
-				if ( withsteps && l !== newpoints.length && l > 0 &&
-					newpoints[ l ] !== null &&
-					newpoints[ l ] !== newpoints[ l - ps ] &&
-					newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ] ) {
-					for (m = 0; m < ps; ++m) {
-						newpoints[ l + ps + m ] = newpoints[ l + m ];
-					}
-					newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];
-				}
-			}
+                    if (l !== newpoints.length && withbottom) {
+                        newpoints[ l + 2 ] = bottom;
+                    }
+                }
+
+                // maintain the line steps invariant
 
-			datapoints.points = newpoints;
-		}
-
-		plot.hooks.processDatapoints.push( computeFillBottoms );
-	}
+                if (withsteps && l !== newpoints.length && l > 0 &&
+                    newpoints[ l ] !== null &&
+                    newpoints[ l ] !== newpoints[ l - ps ] &&
+                    newpoints[ l + 1 ] !== newpoints[ l - ps + 1 ]) {
+                    for (m = 0; m < ps; ++m) {
+                        newpoints[ l + ps + m ] = newpoints[ l + m ];
+                    }
+                    newpoints[ l + 1 ] = newpoints[ l - ps + 1 ];
+                }
+            }
 
-	$.plot.plugins.push({
-		init: init,
-		options: options,
-		name: "fillbetween",
-		version: "1.0"
-	});
+            datapoints.points = newpoints;
+        }
+
+        plot.hooks.processRawData.push(computeFormat);
+        plot.hooks.processDatapoints.push(computeFillBottoms);
+    }
 
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: "fillbetween",
+        version: "1.0"
+    });
 })(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.fillbetween.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.fillbetween.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,1 +1,1 @@
-!function(e){jQuery.plot.plugins.push({init:function(e){function n(e,n){var l;for(l=0;l<n.length;++l)if(n[l].id===e.fillBetween)return n[l];return"number"==typeof e.fillBetween?e.fillBetween<0||e.fillBetween>=n.length?null:n[e.fillBetween]:null}e.hooks.processDatapoints.push(function(e,l,i){if(null!=l.fillBetween){var t=n(l,e.getData());if(t){for(var s,f,o,u,p,r,h,a,g=i.pointsize,w=i.points,B=t.datapoints.pointsize,c=t.datapoints.points,v=[],d=l.lines.show,m=g>2&&i.format[2].y,y=d&&l.lines.steps,b=!0,z=0,D=0;!(z>=w.length);){if(h=v.length,null==w[z]){for(a=0;a<g;++a)v.push(w[z+a]);z+=g}else if(D>=c.length){if(!d)for(a=0;a<g;++a)v.push(w[z+a]);z+=g}else if(null==c[D]){for(a=0;a<g;++a)v.push(null);b=!0,D+=B}else{if(s=w[z],f=w[z+1],u=c[D],p=c[D+1],r=0,s===u){for(a=0;a<g;++a)v.push(w[z+a]);r=p,z+=g,D+=B}else if(s>u){if(d&&z>0&&null!=w[z-g]){for(o=f+(w[z-g+1]-f)*(u-s)/(w[z-g]-s),v.push(u),v.push(o),a=2;a<g;++a)v.push(w[z+a]);r=p}D+=B}else{if(b&&d){z+=g;continue}for(a=0;a<g;++a)v.push(w[z+a]);d&&D>0&&null!=c[D-B]&&(r=p+(c[D-B+1]-p)*(s-u)/(c[D-B]-u)),z+=g}b=!1,h!==v.length&&m&&(v[h+2]=r)}if(y&&h!==v.length&&h>0&&null!==v[h]&&v[h]!==v[h-g]&&v[h+1]!==v[h-g+1]){for(a=0;a<g;++a)v[h+g+a]=v[h+a];v[h+1]=v[h-g+1]}}i.points=v}}})},options:{series:{fillBetween:null}},name:"fillbetween",version:"1.0"})}();
+!function(e){jQuery.plot.plugins.push({init:function(e){function n(e,n){var t;for(t=0;t<n.length;++t)if(n[t].id===e.fillBetween)return n[t];return"number"==typeof e.fillBetween?e.fillBetween<0||e.fillBetween>=n.length?null:n[e.fillBetween]:null}e.hooks.processRawData.push(function(e,n,t,l){null!=n.fillBetween&&(format=l.format,format||(format=[],format.push({x:!0,number:!0,computeRange:"none"!==n.xaxis.options.autoScale,required:!0}),format.push({y:!0,number:!0,computeRange:"none"!==n.yaxis.options.autoScale,required:!0}),void 0!==n.fillBetween&&""!==n.fillBetween&&function(n){var t=e.getData();for(i=0;i<t.length;i++)if(t[i].id===n)return!0;return!1}(n.fillBetween)&&n.fillBetween!==n.id&&format.push({x:!1,y:!0,number:!0,required:!1,computeRange:"none"!==n.yaxis.options.autoScale,defaultValue:0}),l.format=format))}),e.hooks.processDatapoints.push(function(e,t,l){if(null!=t.fillBetween){var i=n(t,e.getData());if(i){for(var o,f,u,s,r,a,p,h,m=l.pointsize,c=l.points,w=i.datapoints.pointsize,g=i.datapoints.points,B=[],d=t.lines.show,y=m>2&&l.format[2].y,v=d&&t.lines.steps,x=!0,b=0,D=0;!(b>=c.length);){if(p=B.length,null==c[b]){for(h=0;h<m;++h)B.push(c[b+h]);b+=m}else if(D>=g.length){if(!d)for(h=0;h<m;++h)B.push(c[b+h]);b+=m}else if(null==g[D]){for(h=0;h<m;++h)B.push(null);x=!0,D+=w}else{if(o=c[b],f=c[b+1],s=g[D],r=g[D+1],a=0,o===s){for(h=0;h<m;++h)B.push(c[b+h]);a=r,b+=m,D+=w}else if(o>s){if(d&&b>0&&null!=c[b-m]){for(u=f+(c[b-m+1]-f)*(s-o)/(c[b-m]-o),B.push(s),B.push(u),h=2;h<m;++h)B.push(c[b+h]);a=r}D+=w}else{if(x&&d){b+=m;continue}for(h=0;h<m;++h)B.push(c[b+h]);d&&D>0&&null!=g[D-w]&&(a=r+(g[D-w+1]-r)*(o-s)/(g[D-w]-s)),b+=m}x=!1,p!==B.length&&y&&(B[p+2]=a)}if(v&&p!==B.length&&p>0&&null!==B[p]&&B[p]!==B[p-m]&&B[p+1]!==B[p-m+1]){for(h=0;h<m;++h)B[p+m+h]=B[p+h];B[p+1]=B[p-m+1]}}l.points=B}}})},options:{series:{fillBetween:null}},name:"fillbetween",version:"1.0"})}();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.flatdata.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,47 @@
+/* Support for flat 1D data series.
+
+A 1D flat data series is a data series in the form of a regular 1D array. The
+main reason for using a flat data series is that it performs better, consumes
+less memory and generates less garbage collection than the regular flot format.
+
+Example:
+
+    plot.setData([[[0,0], [1,1], [2,2], [3,3]]]); // regular flot format
+    plot.setData([{flatdata: true, data: [0, 1, 2, 3]}]); // flatdata format
+
+Set series.flatdata to true to enable this plugin.
+
+You can use series.start to specify the starting index of the series (default is 0)
+You can use series.step to specify the interval between consecutive indexes of the series (default is 1)
+*/
+
+/* global jQuery*/
+
+(function ($) {
+    'use strict';
+
+    function process1DRawData(plot, series, data, datapoints) {
+        if (series.flatdata === true) {
+            var start = series.start || 0;
+            var step = typeof series.step === 'number' ? series.step : 1;
+            datapoints.pointsize = 2;
+            for (var i = 0, j = 0; i < data.length; i++, j += 2) {
+                datapoints.points[j] = start + (i * step);
+                datapoints.points[j + 1] = data[i];
+            }
+            if (datapoints.points !== undefined) {
+                datapoints.points.length = data.length * 2;
+            } else {
+                datapoints.points = [];
+            }
+        }
+    }
+
+    $.plot.plugins.push({
+        init: function(plot) {
+            plot.hooks.processRawData.push(process1DRawData);
+        },
+        name: 'flatdata',
+        version: '0.0.2'
+    });
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.flatdata.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(t){"use strict";function n(t,n,s,i){if(!0===n.flatdata){var o=n.start||0,p="number"==typeof n.step?n.step:1;i.pointsize=2;for(var a=0,e=0;a<s.length;a++,e+=2)i.points[e]=o+a*p,i.points[e+1]=s[a];void 0!==i.points?i.points.length=2*s.length:i.points=[]}}jQuery.plot.plugins.push({init:function(t){t.hooks.processRawData.push(n)},name:"flatdata",version:"0.0.2"})}();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.hover.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,350 @@
+/* global jQuery */
+
+/**
+## jquery.flot.hover.js
+
+This plugin is used for mouse hover and tap on a point of plot series.
+It supports the following options:
+```js
+grid: {
+    hoverable: false, //to trigger plothover event on mouse hover or tap on a point
+    clickable: false //to trigger plotclick event on mouse hover
+}
+```
+
+It listens to native mouse move event or click, as well as artificial generated
+tap and touchevent.
+
+When the mouse is over a point or a tap on a point is performed, that point or
+the correscponding bar will be highlighted and a "plothover" event will be generated.
+
+Custom "touchevent" is triggered when any touch interaction is made. Hover plugin
+handles this events by unhighlighting all of the previously highlighted points and generates
+"plothovercleanup" event to notify any part that is handling plothover (for exemple to cleanup
+the tooltip from webcharts).
+*/
+
+(function($) {
+    'use strict';
+
+    var options = {
+        grid: {
+            hoverable: false,
+            clickable: false
+        }
+    };
+
+    var browser = $.plot.browser;
+
+    var eventType = {
+        click: 'click',
+        hover: 'hover'
+    }
+
+    function init(plot) {
+        var lastMouseMoveEvent;
+        var highlights = [];
+
+        function bindEvents(plot, eventHolder) {
+            var o = plot.getOptions();
+
+            if (o.grid.hoverable || o.grid.clickable) {
+                eventHolder[0].addEventListener('touchevent', triggerCleanupEvent, false);
+                eventHolder[0].addEventListener('tap', generatePlothoverEvent, false);
+            }
+
+            if (o.grid.clickable) {
+                eventHolder.bind("click", onClick);
+            }
+
+            if (o.grid.hoverable) {
+                eventHolder.bind("mousemove", onMouseMove);
+
+                // Use bind, rather than .mouseleave, because we officially
+                // still support jQuery 1.2.6, which doesn't define a shortcut
+                // for mouseenter or mouseleave.  This was a bug/oversight that
+                // was fixed somewhere around 1.3.x.  We can return to using
+                // .mouseleave when we drop support for 1.2.6.
+
+                eventHolder.bind("mouseleave", onMouseLeave);
+            }
+        }
+
+        function shutdown(plot, eventHolder) {
+            eventHolder[0].removeEventListener('tap', generatePlothoverEvent);
+            eventHolder[0].removeEventListener('touchevent', triggerCleanupEvent);
+            eventHolder.unbind("mousemove", onMouseMove);
+            eventHolder.unbind("mouseleave", onMouseLeave);
+            eventHolder.unbind("click", onClick);
+            highlights = [];
+        }
+
+
+        function generatePlothoverEvent(e) {
+            var o = plot.getOptions(),
+                newEvent = new CustomEvent('mouseevent');
+
+            //transform from touch event to mouse event format
+            newEvent.pageX = e.detail.changedTouches[0].pageX;
+            newEvent.pageY = e.detail.changedTouches[0].pageY;
+            newEvent.clientX = e.detail.changedTouches[0].clientX;
+            newEvent.clientY = e.detail.changedTouches[0].clientY;
+
+            if (o.grid.hoverable) {
+                doTriggerClickHoverEvent(newEvent, eventType.hover, 30);
+            }
+            return false;
+        }
+
+        function doTriggerClickHoverEvent(event, eventType, searchDistance) {
+            var series = plot.getData();
+            if (event !== undefined
+                && series.length > 0
+                && series[0].xaxis.c2p !== undefined
+                && series[0].yaxis.c2p !== undefined) {
+                var eventToTrigger = "plot" + eventType;
+                var seriesFlag = eventType + "able";
+                triggerClickHoverEvent(eventToTrigger, event,
+                    function(i) {
+                        return series[i][seriesFlag] !== false;
+                    }, searchDistance);
+            }
+        }
+
+        function onMouseMove(e) {
+            lastMouseMoveEvent = e;
+            plot.getPlaceholder()[0].lastMouseMoveEvent = e;
+            doTriggerClickHoverEvent(e, eventType.hover);
+        }
+
+        function onMouseLeave(e) {
+            lastMouseMoveEvent = undefined;
+            plot.getPlaceholder()[0].lastMouseMoveEvent = undefined;
+            triggerClickHoverEvent("plothover", e,
+                function(i) {
+                    return false;
+                });
+        }
+
+        function onClick(e) {
+            doTriggerClickHoverEvent(e, eventType.click);
+        }
+
+        function triggerCleanupEvent() {
+            plot.unhighlight();
+            plot.getPlaceholder().trigger('plothovercleanup');
+        }
+
+        // trigger click or hover event (they send the same parameters
+        // so we share their code)
+        function triggerClickHoverEvent(eventname, event, seriesFilter, searchDistance) {
+            var options = plot.getOptions(),
+                offset = plot.offset(),
+                page = browser.getPageXY(event),
+                canvasX = page.X - offset.left,
+                canvasY = page.Y - offset.top,
+                pos = plot.c2p({
+                    left: canvasX,
+                    top: canvasY
+                }),
+                distance = searchDistance !== undefined ? searchDistance : options.grid.mouseActiveRadius;
+
+            pos.pageX = page.X;
+            pos.pageY = page.Y;
+
+            var item = plot.findNearbyItem(canvasX, canvasY, seriesFilter, distance);
+
+            if (item) {
+                // fill in mouse pos for any listeners out there
+                item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left, 10);
+                item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top, 10);
+            }
+
+            if (options.grid.autoHighlight) {
+                // clear auto-highlights
+                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])) || !item) {
+                        unhighlight(h.series, h.point);
+                    }
+                }
+
+                if (item) {
+                    highlight(item.series, item.datapoint, eventname);
+                }
+            }
+
+            plot.getPlaceholder().trigger(eventname, [pos, item]);
+        }
+
+        function highlight(s, point, auto) {
+            if (typeof s === "number") {
+                s = plot.getData()[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
+                });
+
+                plot.triggerRedrawOverlay();
+            } else if (!auto) {
+                highlights[i].auto = false;
+            }
+        }
+
+        function unhighlight(s, point) {
+            if (s == null && point == null) {
+                highlights = [];
+                plot.triggerRedrawOverlay();
+                return;
+            }
+
+            if (typeof s === "number") {
+                s = plot.getData()[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);
+
+                plot.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 processDatapoints() {
+            triggerCleanupEvent();
+            doTriggerClickHoverEvent(lastMouseMoveEvent, eventType.hover);
+        }
+
+        function setupGrid() {
+            doTriggerClickHoverEvent(lastMouseMoveEvent, eventType.hover);
+        }
+
+        function drawOverlay(plot, octx, overlay) {
+            var plotOffset = plot.getPlotOffset(),
+                i, hi;
+
+            octx.save();
+            octx.translate(plotOffset.left, plotOffset.top);
+            for (i = 0; i < highlights.length; ++i) {
+                hi = highlights[i];
+
+                if (hi.series.bars.show) drawBarHighlight(hi.series, hi.point, octx);
+                else drawPointHighlight(hi.series, hi.point, octx, plot);
+            }
+            octx.restore();
+        }
+
+        function drawPointHighlight(series, point, octx, plot) {
+            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', 0.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();
+            var symbol = series.points.symbol;
+            if (symbol === 'circle') {
+                octx.arc(x, y, radius, 0, 2 * Math.PI, false);
+            } else if (typeof symbol === 'string' && plot.drawSymbol && plot.drawSymbol[symbol]) {
+                plot.drawSymbol[symbol](octx, x, y, radius, false);
+            }
+
+            octx.closePath();
+            octx.stroke();
+        }
+
+        function drawBarHighlight(series, point, octx) {
+            var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
+                fillStyle = highlightColor,
+                barLeft;
+
+            var barWidth = series.bars.barWidth[0] || series.bars.barWidth;
+            switch (series.bars.align) {
+                case "left":
+                    barLeft = 0;
+                    break;
+                case "right":
+                    barLeft = -barWidth;
+                    break;
+                default:
+                    barLeft = -barWidth / 2;
+            }
+
+            octx.lineWidth = series.bars.lineWidth;
+            octx.strokeStyle = highlightColor;
+
+            var fillTowards = series.bars.fillTowards || 0,
+                bottom = fillTowards > series.yaxis.min ? Math.min(series.yaxis.max, fillTowards) : series.yaxis.min;
+
+            $.plot.drawSeries.drawBar(point[0], point[1], point[2] || bottom, barLeft, barLeft + barWidth,
+                function() {
+                    return fillStyle;
+                }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
+        }
+
+        function initHover(plot, options) {
+            plot.highlight = highlight;
+            plot.unhighlight = unhighlight;
+            if (options.grid.hoverable || options.grid.clickable) {
+                plot.hooks.drawOverlay.push(drawOverlay);
+                plot.hooks.processDatapoints.push(processDatapoints);
+                plot.hooks.setupGrid.push(setupGrid);
+            }
+
+            lastMouseMoveEvent = plot.getPlaceholder()[0].lastMouseMoveEvent;
+        }
+
+        plot.hooks.bindEvents.push(bindEvents);
+        plot.hooks.shutdown.push(shutdown);
+        plot.hooks.processOptions.push(initHover);
+    }
+
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: 'hover',
+        version: '0.1'
+    });
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.hover.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(e){"use strict";var t=e.plot.browser,i={click:"click",hover:"hover"};e.plot.plugins.push({init:function(o){function r(e){var t=o.getOptions(),r=new CustomEvent("mouseevent");return r.pageX=e.detail.changedTouches[0].pageX,r.pageY=e.detail.changedTouches[0].pageY,r.clientX=e.detail.changedTouches[0].clientX,r.clientY=e.detail.changedTouches[0].clientY,t.grid.hoverable&&a(r,i.hover,30),!1}function a(e,t,i){var r=o.getData();if(void 0!==e&&r.length>0&&void 0!==r[0].xaxis.c2p&&void 0!==r[0].yaxis.c2p){var a=t+"able";h("plot"+t,e,function(e){return!1!==r[e][a]},i)}}function n(e){y=e,o.getPlaceholder()[0].lastMouseMoveEvent=e,a(e,i.hover)}function s(e){y=void 0,o.getPlaceholder()[0].lastMouseMoveEvent=void 0,h("plothover",e,function(e){return!1})}function l(e){a(e,i.click)}function c(){o.unhighlight(),o.getPlaceholder().trigger("plothovercleanup")}function h(e,i,r,a){var n=o.getOptions(),s=o.offset(),l=t.getPageXY(i),c=l.X-s.left,h=l.Y-s.top,g=o.c2p({left:c,top:h}),d=void 0!==a?a:n.grid.mouseActiveRadius;g.pageX=l.X,g.pageY=l.Y;var v=o.findNearbyItem(c,h,r,d);if(v&&(v.pageX=parseInt(v.series.xaxis.p2c(v.datapoint[0])+s.left,10),v.pageY=parseInt(v.series.yaxis.p2c(v.datapoint[1])+s.top,10)),n.grid.autoHighlight){for(var f=0;f<k.length;++f){var b=k[f];(b.auto!==e||v&&b.series===v.series&&b.point[0]===v.datapoint[0]&&b.point[1]===v.datapoint[1])&&v||u(b.series,b.point)}v&&p(v.series,v.datapoint,e)}o.getPlaceholder().trigger(e,[g,v])}function p(e,t,i){if("number"==typeof e&&(e=o.getData()[e]),"number"==typeof t){var r=e.datapoints.pointsize;t=e.datapoints.points.slice(r*t,r*(t+1))}var a=g(e,t);-1===a?(k.push({series:e,point:t,auto:i}),o.triggerRedrawOverlay()):i||(k[a].auto=!1)}function u(e,t){if(null==e&&null==t)return k=[],void o.triggerRedrawOverlay();if("number"==typeof e&&(e=o.getData()[e]),"number"==typeof t){var i=e.datapoints.pointsize;t=e.datapoints.points.slice(i*t,i*(t+1))}var r=g(e,t);-1!==r&&(k.splice(r,1),o.triggerRedrawOverlay())}function g(e,t){for(var i=0;i<k.length;++i){var o=k[i];if(o.series===e&&o.point[0]===t[0]&&o.point[1]===t[1])return i}return-1}function d(){c(),a(y,i.hover)}function v(){a(y,i.hover)}function f(e,t,i){var o,r,a=e.getPlotOffset();for(t.save(),t.translate(a.left,a.top),o=0;o<k.length;++o)(r=k[o]).series.bars.show?m(r.series,r.point,t):b(r.series,r.point,t,e);t.restore()}function b(t,i,o,r){var a=i[0],n=i[1],s=t.xaxis,l=t.yaxis,c="string"==typeof t.highlightColor?t.highlightColor:e.color.parse(t.color).scale("a",.5).toString();if(!(a<s.min||a>s.max||n<l.min||n>l.max)){var h=t.points.radius+t.points.lineWidth/2;o.lineWidth=h,o.strokeStyle=c;var p=1.5*h;a=s.p2c(a),n=l.p2c(n),o.beginPath();var u=t.points.symbol;"circle"===u?o.arc(a,n,p,0,2*Math.PI,!1):"string"==typeof u&&r.drawSymbol&&r.drawSymbol[u]&&r.drawSymbol[u](o,a,n,p,!1),o.closePath(),o.stroke()}}function m(t,i,o){var r,a="string"==typeof t.highlightColor?t.highlightColor:e.color.parse(t.color).scale("a",.5).toString(),n=a,s=t.bars.barWidth[0]||t.bars.barWidth;switch(t.bars.align){case"left":r=0;break;case"right":r=-s;break;default:r=-s/2}o.lineWidth=t.bars.lineWidth,o.strokeStyle=a;var l=t.bars.fillTowards||0,c=l>t.yaxis.min?Math.min(t.yaxis.max,l):t.yaxis.min;e.plot.drawSeries.drawBar(i[0],i[1],i[2]||c,r,r+s,function(){return n},t.xaxis,t.yaxis,o,t.bars.horizontal,t.bars.lineWidth)}var y,k=[];o.hooks.bindEvents.push(function(e,t){var i=e.getOptions();(i.grid.hoverable||i.grid.clickable)&&(t[0].addEventListener("touchevent",c,!1),t[0].addEventListener("tap",r,!1)),i.grid.clickable&&t.bind("click",l),i.grid.hoverable&&(t.bind("mousemove",n),t.bind("mouseleave",s))}),o.hooks.shutdown.push(function(e,t){t[0].removeEventListener("tap",r),t[0].removeEventListener("touchevent",c),t.unbind("mousemove",n),t.unbind("mouseleave",s),t.unbind("click",l),k=[]}),o.hooks.processOptions.push(function(e,t){e.highlight=p,e.unhighlight=u,(t.grid.hoverable||t.grid.clickable)&&(e.hooks.drawOverlay.push(f),e.hooks.processDatapoints.push(d),e.hooks.setupGrid.push(v)),y=e.getPlaceholder()[0].lastMouseMoveEvent})},options:{grid:{hoverable:!1,clickable:!1}},name:"hover",version:"0.1"})}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.image.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.image.js	Wed Oct 16 13:00:43 2019 +0200
@@ -11,7 +11,7 @@
 There are two helpers included for retrieving images. The easiest work the way
 that you put in URLs instead of images in the data, like this:
 
-	[ "myimage.png", 0, 0, 10, 10 ]
+    [ "myimage.png", 0, 0, 10, 10 ]
 
 Then call $.plot.image.loadData( data, options, callback ) where data and
 options are the same as you pass in to $.plot. This loads the images, replaces
@@ -25,20 +25,20 @@
 
 The plugin supports these options:
 
-	series: {
-		images: {
-			show: boolean
-			anchor: "corner" or "center"
-			alpha: [ 0, 1 ]
-		}
-	}
+    series: {
+        images: {
+            show: boolean
+            anchor: "corner" or "center"
+            alpha: [ 0, 1 ]
+        }
+    }
 
 They can be specified for a specific series:
 
-	$.plot( $("#placeholder"), [{
-		data: [ ... ],
-		images: { ... }
-	])
+    $.plot( $("#placeholder"), [{
+        data: [ ... ],
+        images: { ... }
+    ])
 
 Note that because the data format is different from usual data points, you
 can't use images with anything else in a specific data series.
@@ -69,16 +69,18 @@
         var urls = [], points = [];
 
         var defaultShow = options.series.images.show;
-        
+
         $.each(series, function (i, s) {
-            if (!(defaultShow || s.images.show))
+            if (!(defaultShow || s.images.show)) {
                 return;
-            
-            if (s.data)
+            }
+
+            if (s.data) {
                 s = s.data;
+            }
 
             $.each(s, function (i, p) {
-                if (typeof p[0] == "string") {
+                if (typeof p[0] === "string") {
                     urls.push(p[0]);
                     points.push(p);
                 }
@@ -88,42 +90,45 @@
         $.plot.image.load(urls, function (loadedImages) {
             $.each(points, function (i, p) {
                 var url = p[0];
-                if (loadedImages[url])
+                if (loadedImages[url]) {
                     p[0] = loadedImages[url];
+                }
             });
 
             callback();
         });
     }
-    
+
     $.plot.image.load = function (urls, callback) {
         var missing = urls.length, loaded = {};
-        if (missing == 0)
+        if (missing === 0) {
             callback({});
+        }
 
         $.each(urls, function (i, url) {
             var handler = function () {
                 --missing;
-                
                 loaded[url] = this;
-                
-                if (missing == 0)
+
+                if (missing === 0) {
                     callback(loaded);
+                }
             };
 
             $('<img />').load(handler).error(handler).attr('src', url);
         });
     };
-    
+
     function drawSeries(plot, ctx, series) {
         var plotOffset = plot.getPlotOffset();
-        
-        if (!series.images || !series.images.show)
+
+        if (!series.images || !series.images.show) {
             return;
-        
+        }
+
         var points = series.datapoints.points,
             ps = series.datapoints.pointsize;
-        
+
         for (var i = 0; i < points.length; i += ps) {
             var img = points[i],
                 x1 = points[i + 1], y1 = points[i + 2],
@@ -134,8 +139,9 @@
             // actually we should check img.complete, but it
             // appears to be a somewhat unreliable indicator in
             // IE6 (false even after load event)
-            if (!img || img.width <= 0 || img.height <= 0)
+            if (!img || img.width <= 0 || img.height <= 0) {
                 continue;
+            }
 
             if (x1 > x2) {
                 tmp = x2;
@@ -147,23 +153,24 @@
                 y2 = y1;
                 y1 = tmp;
             }
-            
-            // if the anchor is at the center of the pixel, expand the 
+
+            // if the anchor is at the center of the pixel, expand the
             // image by 1/2 pixel in each direction
-            if (series.images.anchor == "center") {
-                tmp = 0.5 * (x2-x1) / (img.width - 1);
+            if (series.images.anchor === "center") {
+                tmp = 0.5 * (x2 - x1) / (img.width - 1);
                 x1 -= tmp;
                 x2 += tmp;
-                tmp = 0.5 * (y2-y1) / (img.height - 1);
+                tmp = 0.5 * (y2 - y1) / (img.height - 1);
                 y1 -= tmp;
                 y2 += tmp;
             }
-            
+
             // clip
-            if (x1 == x2 || y1 == y2 ||
+            if (x1 === x2 || y1 === y2 ||
                 x1 >= xaxis.max || x2 <= xaxis.min ||
-                y1 >= yaxis.max || y2 <= yaxis.min)
+                y1 >= yaxis.max || y2 <= yaxis.min) {
                 continue;
+            }
 
             var sx1 = 0, sy1 = 0, sx2 = img.width, sy2 = img.height;
             if (x1 < xaxis.min) {
@@ -185,12 +192,12 @@
                 sy1 += (sy1 - sy2) * (yaxis.max - y2) / (y2 - y1);
                 y2 = yaxis.max;
             }
-            
+
             x1 = xaxis.p2c(x1);
             x2 = xaxis.p2c(x2);
             y1 = yaxis.p2c(y1);
             y2 = yaxis.p2c(y2);
-            
+
             // the transformation may have swapped us
             if (x1 > x2) {
                 tmp = x2;
@@ -214,8 +221,9 @@
     }
 
     function processRawData(plot, series, data, datapoints) {
-        if (!series.images.show)
+        if (!series.images.show) {
             return;
+        }
 
         // format is Image, x1, y1, x2, y2 (opposite corners)
         datapoints.format = [
@@ -226,12 +234,12 @@
             { y: true, number: true, required: true }
         ];
     }
-    
+
     function init(plot) {
         plot.hooks.processRawData.push(processRawData);
         plot.hooks.drawSeries.push(drawSeries);
     }
-    
+
     $.plot.plugins.push({
         init: init,
         options: options,
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.image.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.image.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,7 +1,1 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
-
-Copyright (c) 2007-2014 IOLA and Ole Laursen.
-Licensed under the MIT license.
-
-*/
-(function($){var options={series:{images:{show:false,alpha:1,anchor:"corner"}}};$.plot.image={};$.plot.image.loadDataImages=function(series,options,callback){var urls=[],points=[];var defaultShow=options.series.images.show;$.each(series,function(i,s){if(!(defaultShow||s.images.show))return;if(s.data)s=s.data;$.each(s,function(i,p){if(typeof p[0]=="string"){urls.push(p[0]);points.push(p)}})});$.plot.image.load(urls,function(loadedImages){$.each(points,function(i,p){var url=p[0];if(loadedImages[url])p[0]=loadedImages[url]});callback()})};$.plot.image.load=function(urls,callback){var missing=urls.length,loaded={};if(missing==0)callback({});$.each(urls,function(i,url){var handler=function(){--missing;loaded[url]=this;if(missing==0)callback(loaded)};$("<img />").load(handler).error(handler).attr("src",url)})};function drawSeries(plot,ctx,series){var plotOffset=plot.getPlotOffset();if(!series.images||!series.images.show)return;var points=series.datapoints.points,ps=series.datapoints.pointsize;for(var i=0;i<points.length;i+=ps){var img=points[i],x1=points[i+1],y1=points[i+2],x2=points[i+3],y2=points[i+4],xaxis=series.xaxis,yaxis=series.yaxis,tmp;if(!img||img.width<=0||img.height<=0)continue;if(x1>x2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}if(series.images.anchor=="center"){tmp=.5*(x2-x1)/(img.width-1);x1-=tmp;x2+=tmp;tmp=.5*(y2-y1)/(img.height-1);y1-=tmp;y2+=tmp}if(x1==x2||y1==y2||x1>=xaxis.max||x2<=xaxis.min||y1>=yaxis.max||y2<=yaxis.min)continue;var sx1=0,sy1=0,sx2=img.width,sy2=img.height;if(x1<xaxis.min){sx1+=(sx2-sx1)*(xaxis.min-x1)/(x2-x1);x1=xaxis.min}if(x2>xaxis.max){sx2+=(sx2-sx1)*(xaxis.max-x2)/(x2-x1);x2=xaxis.max}if(y1<yaxis.min){sy2+=(sy1-sy2)*(yaxis.min-y1)/(y2-y1);y1=yaxis.min}if(y2>yaxis.max){sy1+=(sy1-sy2)*(yaxis.max-y2)/(y2-y1);y2=yaxis.max}x1=xaxis.p2c(x1);x2=xaxis.p2c(x2);y1=yaxis.p2c(y1);y2=yaxis.p2c(y2);if(x1>x2){tmp=x2;x2=x1;x1=tmp}if(y1>y2){tmp=y2;y2=y1;y1=tmp}tmp=ctx.globalAlpha;ctx.globalAlpha*=series.images.alpha;ctx.drawImage(img,sx1,sy1,sx2-sx1,sy2-sy1,x1+plotOffset.left,y1+plotOffset.top,x2-x1,y2-y1);ctx.globalAlpha=tmp}}function processRawData(plot,series,data,datapoints){if(!series.images.show)return;datapoints.format=[{required:true},{x:true,number:true,required:true},{y:true,number:true,required:true},{x:true,number:true,required:true},{y:true,number:true,required:true}]}function init(plot){plot.hooks.processRawData.push(processRawData);plot.hooks.drawSeries.push(drawSeries)}$.plot.plugins.push({init:init,options:options,name:"image",version:"1.1"})})(jQuery);
\ No newline at end of file
+!function(a){function i(a,i,e){var n=a.getPlotOffset();if(e.images&&e.images.show)for(var o=e.datapoints.points,t=e.datapoints.pointsize,r=0;r<o.length;r+=t){var s,m=o[r],h=o[r+1],p=o[r+2],u=o[r+3],g=o[r+4],c=e.xaxis,l=e.yaxis;if(!(!m||m.width<=0||m.height<=0)&&(h>u&&(s=u,u=h,h=s),p>g&&(s=g,g=p,p=s),"center"===e.images.anchor&&(h-=s=.5*(u-h)/(m.width-1),u+=s,p-=s=.5*(g-p)/(m.height-1),g+=s),!(h===u||p===g||h>=c.max||u<=c.min||p>=l.max||g<=l.min))){var f=0,d=0,x=m.width,w=m.height;h<c.min&&(f+=(x-f)*(c.min-h)/(u-h),h=c.min),u>c.max&&(x+=(x-f)*(c.max-u)/(u-h),u=c.max),p<l.min&&(w+=(d-w)*(l.min-p)/(g-p),p=l.min),g>l.max&&(d+=(d-w)*(l.max-g)/(g-p),g=l.max),h=c.p2c(h),u=c.p2c(u),p=l.p2c(p),g=l.p2c(g),h>u&&(s=u,u=h,h=s),p>g&&(s=g,g=p,p=s),s=i.globalAlpha,i.globalAlpha*=e.images.alpha,i.drawImage(m,f,d,x-f,w-d,h+n.left,p+n.top,u-h,g-p),i.globalAlpha=s}}}function e(a,i,e,n){i.images.show&&(n.format=[{required:!0},{x:!0,number:!0,required:!0},{y:!0,number:!0,required:!0},{x:!0,number:!0,required:!0},{y:!0,number:!0,required:!0}])}a.plot.image={},a.plot.image.loadDataImages=function(i,e,n){var o=[],t=[],r=e.series.images.show;a.each(i,function(i,e){(r||e.images.show)&&(e.data&&(e=e.data),a.each(e,function(a,i){"string"==typeof i[0]&&(o.push(i[0]),t.push(i))}))}),a.plot.image.load(o,function(i){a.each(t,function(a,e){var n=e[0];i[n]&&(e[0]=i[n])}),n()})},a.plot.image.load=function(i,e){var n=i.length,o={};0===n&&e({}),a.each(i,function(i,t){var r=function(){--n,o[t]=this,0===n&&e(o)};a("<img />").load(r).error(r).attr("src",t)})},a.plot.plugins.push({init:function(a){a.hooks.processRawData.push(e),a.hooks.drawSeries.push(i)},options:{series:{images:{show:!1,alpha:1,anchor:"corner"}}},name:"image",version:"1.1"})}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,510 +1,98 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
+/* Javascript plotting library for jQuery, version 3.0.0.
 
 Copyright (c) 2007-2014 IOLA and Ole Laursen.
 Licensed under the MIT license.
 
 */
 
-// first an inline dependency, jquery.colorhelpers.js, we inline it here
-// for convenience
-
-/* Plugin for jQuery for working with colors.
- *
- * Version 1.1.
- *
- * Inspiration from jQuery color animation plugin by John Resig.
- *
- * Released under the MIT license by Ole Laursen, October 2009.
- *
- * Examples:
- *
- *   $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
- *   var c = $.color.extract($("#mydiv"), 'background-color');
- *   console.log(c.r, c.g, c.b, c.a);
- *   $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
- *
- * Note that .scale() and .add() return the same modified object
- * instead of making a new one.
- *
- * V. 1.1: Fix error handling so e.g. parsing an empty string does
- * produce a color rather than just crashing.
- */
-(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);
-
 // the actual Flot code
 (function($) {
-
-	// Cache the prototype hasOwnProperty for faster access
-
-	var hasOwnProperty = Object.prototype.hasOwnProperty;
-
-    // A shim to provide 'detach' to jQuery versions prior to 1.4.  Using a DOM
-    // operation produces the same effect as detach, i.e. removing the element
-    // without touching its jQuery data.
-
-    // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+.
-
-    if (!$.fn.detach) {
-        $.fn.detach = function() {
-            return this.each(function() {
-                if (this.parentNode) {
-                    this.parentNode.removeChild( this );
-                }
-            });
-        };
+    "use strict";
+
+    var Canvas = window.Flot.Canvas;
+
+    function defaultTickGenerator(axis) {
+        var ticks = [],
+            start = $.plot.saturated.saturate($.plot.saturated.floorInBase(axis.min, axis.tickSize)),
+            i = 0,
+            v = Number.NaN,
+            prev;
+
+        if (start === -Number.MAX_VALUE) {
+            ticks.push(start);
+            start = $.plot.saturated.floorInBase(axis.min + axis.tickSize, axis.tickSize);
+        }
+
+        do {
+            prev = v;
+            //v = start + i * axis.tickSize;
+            v = $.plot.saturated.multiplyAdd(axis.tickSize, i, start);
+            ticks.push(v);
+            ++i;
+        } while (v < axis.max && v !== prev);
+
+        return ticks;
     }
 
-	///////////////////////////////////////////////////////////////////////////
-	// The Canvas object is a wrapper around an HTML5 <canvas> tag.
-	//
-	// @constructor
-	// @param {string} cls List of classes to apply to the canvas.
-	// @param {element} container Element onto which to append the canvas.
-	//
-	// Requiring a container is a little iffy, but unfortunately canvas
-	// operations don't work unless the canvas is attached to the DOM.
-
-	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 HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas
-
-			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");
-
-		// Determine the screen's ratio of physical to device-independent
-		// pixels.  This is the ratio between the canvas width that the browser
-		// advertises and the number of pixels actually present in that space.
-
-		// The iPhone 4, for example, has a device-independent width of 320px,
-		// but its screen is actually 640px wide.  It therefore has a pixel
-		// ratio of 2, while most normal devices have a ratio of 1.
-
-		var devicePixelRatio = window.devicePixelRatio || 1,
-			backingStoreRatio =
-				context.webkitBackingStorePixelRatio ||
-				context.mozBackingStorePixelRatio ||
-				context.msBackingStorePixelRatio ||
-				context.oBackingStorePixelRatio ||
-				context.backingStorePixelRatio || 1;
-
-		this.pixelRatio = devicePixelRatio / backingStoreRatio;
-
-		// Size the canvas to match the internal dimensions of its container
-
-		this.resize(container.width(), container.height());
-
-		// Collection of HTML div layers for text overlaid onto the canvas
-
-		this.textContainer = null;
-		this.text = {};
-
-		// Cache of text fragments and metrics, so we can avoid expensively
-		// re-calculating them when the plot is re-rendered in a loop.
-
-		this._textCache = {};
-	}
-
-	// Resizes the canvas to the given dimensions.
-	//
-	// @param {number} width New width of the canvas, in pixels.
-	// @param {number} width New height of the canvas, in pixels.
-
-	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;
-
-		// Resize the canvas, increasing its density based on the display's
-		// pixel ratio; basically giving it more pixels without increasing the
-		// size of its element, to take advantage of the fact that retina
-		// displays have that many more pixels in the same advertised space.
-
-		// Resizing should reset the state (excanvas seems to be buggy though)
-
-		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;
-		}
-
-		// Save the context, so we can reset in case we get replotted.  The
-		// restore ensure that we're really back at the initial state, and
-		// should be safe even if we haven't saved the initial state yet.
-
-		context.restore();
-		context.save();
-
-		// Scale the coordinate space to match the display density; so even though we
-		// may have twice as many pixels, we still want lines and other drawing to
-		// appear at the same size; the extra pixels will just make them crisper.
-
-		context.scale(pixelRatio, pixelRatio);
-	};
-
-	// Clears the entire canvas area, not including any overlaid HTML text
-
-	Canvas.prototype.clear = function() {
-		this.context.clearRect(0, 0, this.width, this.height);
-	};
-
-	// Finishes rendering the canvas, including managing the text overlay.
-
-	Canvas.prototype.render = function() {
-
-		var cache = this._textCache;
-
-		// For each text layer, add elements marked as active that haven't
-		// already been rendered, and remove those that are no longer active.
-
-		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();
-			}
-		}
-	};
-
-	// Creates (if necessary) and returns the text overlay container.
-	//
-	// @param {string} classes String of space-separated CSS classes used to
-	//     uniquely identify the text layer.
-	// @return {object} The jQuery-wrapped text-layer div.
-
-	Canvas.prototype.getTextLayer = function(classes) {
-
-		var layer = this.text[classes];
-
-		// Create the text layer if it doesn't exist
-
-		if (layer == null) {
-
-			// Create the text layer container, if it doesn't exist
-
-			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;
-	};
-
-	// Creates (if necessary) and returns a text info object.
-	//
-	// The object looks like this:
-	//
-	// {
-	//     width: Width of the text's wrapper div.
-	//     height: Height of the text's wrapper div.
-	//     element: The jQuery-wrapped HTML div containing the text.
-	//     positions: Array of positions at which this text is drawn.
-	// }
-	//
-	// The positions array contains objects that look like this:
-	//
-	// {
-	//     active: Flag indicating whether the text should be visible.
-	//     rendered: Flag indicating whether the text is currently visible.
-	//     element: The jQuery-wrapped HTML div containing the text.
-	//     x: X coordinate at which to draw the text.
-	//     y: Y coordinate at which to draw the text.
-	// }
-	//
-	// Each position after the first receives a clone of the original element.
-	//
-	// The idea is that that the width, height, and general 'identity' of the
-	// text is constant no matter where it is placed; the placements are a
-	// secondary property.
-	//
-	// Canvas maintains a cache of recently-used text info objects; getTextInfo
-	// either returns the cached element or creates a new entry.
-	//
-	// @param {string} layer A string of space-separated CSS classes uniquely
-	//     identifying the layer containing this text.
-	// @param {string} text Text string to retrieve info for.
-	// @param {(string|object)=} font Either a string of space-separated CSS
-	//     classes or a font-spec object, defining the text's font and style.
-	// @param {number=} angle Angle at which to rotate the text, in degrees.
-	//     Angle is currently unused, it will be implemented in the future.
-	// @param {number=} width Maximum width of the text before it wraps.
-	// @return {object} a text info object.
-
-	Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {
-
-		var textStyle, layerCache, styleCache, info;
-
-		// Cast the value to a string, in case we were given a number or such
-
-		text = "" + text;
-
-		// If the font is a font-spec object, generate a CSS font definition
-
-		if (typeof font === "object") {
-			textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
-		} else {
-			textStyle = font;
-		}
-
-		// Retrieve (or create) the cache for the text's layer and styles
-
-		layerCache = this._textCache[layer];
-
-		if (layerCache == null) {
-			layerCache = this._textCache[layer] = {};
-		}
-
-		styleCache = layerCache[textStyle];
-
-		if (styleCache == null) {
-			styleCache = layerCache[textStyle] = {};
-		}
-
-		info = styleCache[text];
-
-		// If we can't find a matching element in our cache, create a new one
-
-		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;
-	};
-
-	// Adds a text string to the canvas text overlay.
-	//
-	// The text isn't drawn immediately; it is marked as rendering, which will
-	// result in its addition to the canvas on the next render pass.
-	//
-	// @param {string} layer A string of space-separated CSS classes uniquely
-	//     identifying the layer containing this text.
-	// @param {number} x X coordinate at which to draw the text.
-	// @param {number} y Y coordinate at which to draw the text.
-	// @param {string} text Text string to draw.
-	// @param {(string|object)=} font Either a string of space-separated CSS
-	//     classes or a font-spec object, defining the text's font and style.
-	// @param {number=} angle Angle at which to rotate the text, in degrees.
-	//     Angle is currently unused, it will be implemented in the future.
-	// @param {number=} width Maximum width of the text before it wraps.
-	// @param {string=} halign Horizontal alignment of the text; either "left",
-	//     "center" or "right".
-	// @param {string=} valign Vertical alignment of the text; either "top",
-	//     "middle" or "bottom".
-
-	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;
-
-		// Tweak the div's position to match the text's alignment
-
-		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;
-		}
-
-		// Determine whether this text already exists at this position.
-		// If so, mark it for inclusion in the next render pass.
-
-		for (var i = 0, position; position = positions[i]; i++) {
-			if (position.x == x && position.y == y) {
-				position.active = true;
-				return;
-			}
-		}
-
-		// If the text doesn't exist at this position, create a new entry
-
-		// For the very first position we'll re-use the original element,
-		// while for subsequent ones we'll clone it.
-
-		position = {
-			active: true,
-			rendered: false,
-			element: positions.length ? info.element.clone() : info.element,
-			x: x,
-			y: y
-		};
-
-		positions.push(position);
-
-		// Move the element to its final position within the container
-
-		position.element.css({
-			top: Math.round(y),
-			left: Math.round(x),
-			'text-align': halign	// In case the text wraps
-		});
-	};
-
-	// Removes one or more text strings from the canvas text overlay.
-	//
-	// If no parameters are given, all text within the layer is removed.
-	//
-	// Note that the text is not immediately removed; it is simply marked as
-	// inactive, which will result in its removal on the next render pass.
-	// This avoids the performance penalty for 'clear and redraw' behavior,
-	// where we potentially get rid of all text on a layer, but will likely
-	// add back most or all of it later, as when redrawing axes, for example.
-	//
-	// @param {string} layer A string of space-separated CSS classes uniquely
-	//     identifying the layer containing this text.
-	// @param {number=} x X coordinate of the text.
-	// @param {number=} y Y coordinate of the text.
-	// @param {string=} text Text string to remove.
-	// @param {(string|object)=} font Either a string of space-separated CSS
-	//     classes or a font-spec object, defining the text's font and style.
-	// @param {number=} angle Angle at which the text is rotated, in degrees.
-	//     Angle is currently unused, it will be implemented in the future.
-
-	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;
-				}
-			}
-		}
-	};
-
-	///////////////////////////////////////////////////////////////////////////
-	// The top-level container for the entire plot.
-
+    function defaultTickFormatter(value, axis, precision) {
+        var oldTickDecimals = axis.tickDecimals,
+            expPosition = ("" + value).indexOf("e");
+
+        if (expPosition !== -1) {
+            return expRepTickFormatter(value, axis, precision);
+        }
+
+        if (precision > 0) {
+            axis.tickDecimals = precision;
+        }
+
+        var factor = axis.tickDecimals ? parseFloat('1e' + axis.tickDecimals) : 1,
+            formatted = "" + Math.round(value * factor) / factor;
+
+        // If tickDecimals was specified, ensure that we have exactly that
+        // much precision; otherwise default to the value's own precision.
+        if (axis.tickDecimals != null) {
+            var decimal = formatted.indexOf("."),
+                decimalPrecision = decimal === -1 ? 0 : formatted.length - decimal - 1;
+            if (decimalPrecision < axis.tickDecimals) {
+                var decimals = ("" + factor).substr(1, axis.tickDecimals - decimalPrecision);
+                formatted = (decimalPrecision ? formatted : formatted + ".") + decimals;
+            }
+        }
+
+        axis.tickDecimals = oldTickDecimals;
+        return formatted;
+    };
+
+    function expRepTickFormatter(value, axis, precision) {
+        var expPosition = ("" + value).indexOf("e"),
+            exponentValue = parseInt(("" + value).substr(expPosition + 1)),
+            tenExponent = expPosition !== -1 ? exponentValue : (value > 0 ? Math.floor(Math.log(value) / Math.LN10) : 0),
+            roundWith = parseFloat('1e' + tenExponent),
+            x = value / roundWith;
+
+        if (precision) {
+            var updatedPrecision = recomputePrecision(value, precision);
+            return (value / roundWith).toFixed(updatedPrecision) + 'e' + tenExponent;
+        }
+
+        if (axis.tickDecimals > 0) {
+            return x.toFixed(recomputePrecision(value, axis.tickDecimals)) + 'e' + tenExponent;
+        }
+        return x.toFixed() + 'e' + tenExponent;
+    }
+
+    function recomputePrecision(num, precision) {
+        //for numbers close to zero, the precision from flot will be a big number
+        //while for big numbers, the precision will be negative
+        var log10Value = Math.log(Math.abs(num)) * Math.LOG10E,
+            newPrecision = Math.abs(log10Value + precision);
+
+        return newPrecision <= 20 ? Math.floor(newPrecision) : 20;
+    }
+
+    ///////////////////////////////////////////////////////////////////////////
+    // The top-level container for the entire plot.
     function Plot(placeholder, data_, options_, plugins) {
         // data is on the form:
         //   [ series1, series2 ... ]
@@ -515,18 +103,6 @@
             options = {
                 // the color theme used for graphs
                 colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
-                legend: {
-                    show: true,
-                    noColumns: 1, // number of colums in legend table
-                    labelFormatter: null, // fn: string -> string
-                    labelBoxBorderColor: "#ccc", // border color for the little label boxes
-                    container: null, // container (as jQuery object) to put legend in, null means default on top of graph
-                    position: "ne", // position of default legend container within plot
-                    margin: 5, // distance from grid edge to default legend container within plot
-                    backgroundColor: null, // null means auto-detect
-                    backgroundOpacity: 0.85, // set to 0 to avoid background
-                    sorted: null    // default to no legend sorting
-                },
                 xaxis: {
                     show: null, // null = auto-detect, true = always, false = never
                     position: "bottom", // or "top"
@@ -538,21 +114,35 @@
                     inverseTransform: null, // if transform is set, this should be the inverse function
                     min: null, // min. value to show, null means set automatically
                     max: null, // max. value to show, null means set automatically
-                    autoscaleMargin: null, // margin in % to add if auto-setting min/max
+                    autoScaleMargin: null, // margin in % to add if autoScale option is on "loose" mode,
+                    autoScale: "exact", // Available modes: "none", "loose", "exact", "sliding-window"
+                    windowSize: null, // null or number. This is the size of sliding-window.
+                    growOnly: null, // grow only, useful for smoother auto-scale, the scales will grow to accomodate data but won't shrink back.
                     ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
                     tickFormatter: null, // fn: number -> string
+                    showTickLabels: "major", // "none", "endpoints", "major", "all"
                     labelWidth: null, // size of tick labels in pixels
                     labelHeight: null,
                     reserveSpace: null, // whether to reserve space even if axis isn't shown
-                    tickLength: null, // size in pixels of ticks, or "full" for whole line
+                    tickLength: null, // size in pixels of major tick marks
+                    showMinorTicks: null, // true = show minor tick marks, false = hide minor tick marks
+                    showTicks: null, // true = show tick marks, false = hide all tick marks
+                    gridLines: null, // true = show grid lines, false = hide grid lines
                     alignTicksWithAxis: null, // axis number or null for no sync
                     tickDecimals: null, // no. of decimals, null means auto
                     tickSize: null, // number or [number, "unit"]
-                    minTickSize: null // number or [number, "unit"]
+                    minTickSize: null, // number or [number, "unit"]
+                    offset: { below: 0, above: 0 }, // the plot drawing offset. this is calculated by the flot.navigate for each axis
+                    boxPosition: { centerX: 0, centerY: 0 } //position of the axis on the corresponding axis box
                 },
                 yaxis: {
-                    autoscaleMargin: 0.02,
-                    position: "left" // or "right"
+                    autoScaleMargin: 0.02, // margin in % to add if autoScale option is on "loose" mode
+                    autoScale: "loose", // Available modes: "none", "loose", "exact"
+                    growOnly: null, // grow only, useful for smoother auto-scale, the scales will grow to accomodate data but won't shrink back.
+                    position: "left", // or "right"
+                    showTickLabels: "major", // "none", "endpoints", "major", "all"
+                    offset: { below: 0, above: 0 }, // the plot drawing offset. this is calculated by the flot.navigate for each axis
+                    boxPosition: { centerX: 0, centerY: 0 } //position of the axis on the corresponding axis box
                 },
                 xaxes: [],
                 yaxes: [],
@@ -563,12 +153,12 @@
                         lineWidth: 2, // in pixels
                         fill: true,
                         fillColor: "#ffffff",
-                        symbol: "circle" // or callback
+                        symbol: 'circle' // or callback
                     },
                     lines: {
                         // we don't put in show: false so we can see
                         // whether lines were actively disabled
-                        lineWidth: 2, // in pixels
+                        lineWidth: 1, // in pixels
                         fill: false,
                         fillColor: null,
                         steps: false
@@ -578,11 +168,14 @@
                     bars: {
                         show: false,
                         lineWidth: 2, // in pixels
-                        barWidth: 1, // in units of the x axis
+                        // barWidth: number or [number, absolute]
+                        // when 'absolute' is false, 'number' is relative to the minimum distance between points for the series
+                        // when 'absolute' is true, 'number' is considered to be in units of the x-axis
+                        horizontal: false,
+                        barWidth: 0.8,
                         fill: true,
                         fillColor: null,
                         align: "left", // "left", "right", or "center"
-                        horizontal: false,
                         zero: true
                     },
                     shadowSize: 3,
@@ -598,7 +191,7 @@
                     margin: 0, // distance from the canvas edge to the grid
                     labelMargin: 5, // in pixels
                     axisMargin: 8, // in pixels
-                    borderWidth: 2, // in pixels
+                    borderWidth: 1, // in pixels
                     minBorderMargin: null, // in pixels, null means taken from points radius
                     markings: null, // array of ranges or fn: axes -> array of ranges
                     markingsColor: "#f4f4f4",
@@ -607,65 +200,108 @@
                     clickable: false,
                     hoverable: false,
                     autoHighlight: true, // highlight in case mouse is near
-                    mouseActiveRadius: 10 // how far the mouse can be away to activate an item
+                    mouseActiveRadius: 15 // how far the mouse can be away to activate an item
                 },
                 interaction: {
-                    redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
+                    redrawOverlayInterval: 1000 / 60 // time between updates, -1 means in same flow
                 },
                 hooks: {}
             },
-        surface = null,     // the canvas for the plot itself
-        overlay = null,     // canvas for interactive stuff on top of plot
-        eventHolder = null, // jQuery object that events should be bound to
-        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;
+            surface = null, // the canvas for the plot itself
+            overlay = null, // canvas for interactive stuff on top of plot
+            eventHolder = null, // jQuery object that events should be bound to
+            ctx = null,
+            octx = null,
+            xaxes = [],
+            yaxes = [],
+            plotOffset = {
+                left: 0,
+                right: 0,
+                top: 0,
+                bottom: 0
+            },
+            plotWidth = 0,
+            plotHeight = 0,
+            hooks = {
+                processOptions: [],
+                processRawData: [],
+                processDatapoints: [],
+                processOffset: [],
+                setupGrid: [],
+                adjustSeriesDataRange: [],
+                setRange: [],
+                drawBackground: [],
+                drawSeries: [],
+                drawAxis: [],
+                draw: [],
+                axisReserveSpace: [],
+                bindEvents: [],
+                drawOverlay: [],
+                resize: [],
+                shutdown: []
+            },
+            plot = this;
+
+        var eventManager = {};
+
+        // interactive features
+
+        var redrawTimeout = null;
 
         // public functions
         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 () {
+        plot.getPlaceholder = function() {
+            return placeholder;
+        };
+        plot.getCanvas = function() {
+            return surface.element;
+        };
+        plot.getSurface = function() {
+            return surface;
+        };
+        plot.getEventHolder = function() {
+            return eventHolder[0];
+        };
+        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;
+        plot.getData = function() {
+            return series;
+        };
+        plot.getAxes = function() {
+            var res = {};
+            $.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.getXAxes = function() {
+            return xaxes;
+        };
+        plot.getYAxes = function() {
+            return yaxes;
+        };
+        plot.c2p = canvasToCartesianAxisCoords;
+        plot.p2c = cartesianAxisToCanvasCoords;
+        plot.getOptions = function() {
+            return options;
+        };
         plot.triggerRedrawOverlay = triggerRedrawOverlay;
         plot.pointOffset = function(point) {
             return {
@@ -674,7 +310,7 @@
             };
         };
         plot.shutdown = shutdown;
-        plot.destroy = function () {
+        plot.destroy = function() {
             shutdown();
             placeholder.removeData("plot").empty();
 
@@ -688,37 +324,54 @@
             xaxes = [];
             yaxes = [];
             hooks = null;
-            highlights = [];
             plot = null;
         };
-        plot.resize = function () {
-        	var width = placeholder.width(),
-        		height = placeholder.height();
+
+        plot.resize = function() {
+            var width = placeholder.width(),
+                height = placeholder.height();
             surface.resize(width, height);
             overlay.resize(width, height);
+
+            executeHooks(hooks.resize, [width, height]);
         };
 
+        plot.clearTextCache = function () {
+            surface.clearCache();
+            overlay.clearCache();
+        };
+
+        plot.autoScaleAxis = autoScaleAxis;
+        plot.computeRangeForDataSeries = computeRangeForDataSeries;
+        plot.adjustSeriesDataRange = adjustSeriesDataRange;
+        plot.findNearbyItem = findNearbyItem;
+        plot.findNearbyInterpolationPoint = findNearbyInterpolationPoint;
+        plot.computeValuePrecision = computeValuePrecision;
+        plot.computeTickSize = computeTickSize;
+        plot.addEventHandler = addEventHandler;
+
         // public attributes
         plot.hooks = hooks;
 
         // initialize
+        var MINOR_TICKS_COUNT_CONSTANT = $.plot.uiConstants.MINOR_TICKS_COUNT_CONSTANT;
+        var TICK_LENGTH_CONSTANT = $.plot.uiConstants.TICK_LENGTH_CONSTANT;
         initPlugins(plot);
-        parseOptions(options_);
         setupCanvases();
+        parseOptions(options_);
         setData(data_);
-        setupGrid();
+        setupGrid(true);
         draw();
         bindEvents();
 
-
         function executeHooks(hook, args) {
             args = [plot].concat(args);
-            for (var i = 0; i < hook.length; ++i)
+            for (var i = 0; i < hook.length; ++i) {
                 hook[i].apply(this, args);
+            }
         }
 
         function initPlugins() {
-
             // References to key classes, allowing plugins to modify them
 
             var classes = {
@@ -728,13 +381,13 @@
             for (var i = 0; i < plugins.length; ++i) {
                 var p = plugins[i];
                 p.init(plot, classes);
-                if (p.options)
+                if (p.options) {
                     $.extend(true, options, p.options);
+                }
             }
         }
 
         function parseOptions(opts) {
-
             $.extend(true, options, opts);
 
             // $.extend merges arrays, rather than replacing them.  When less
@@ -743,23 +396,34 @@
             // not expected behavior; avoid it by replacing them here.
 
             if (opts && opts.colors) {
-            	options.colors = opts.colors;
+                options.colors = opts.colors;
             }
 
-            if (options.xaxis.color == null)
+            if (options.xaxis.color == null) {
                 options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
-            if (options.yaxis.color == null)
+            }
+
+            if (options.yaxis.color == null) {
                 options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
-
-            if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
+            }
+
+            if (options.xaxis.tickColor == null) {
+                // grid.tickColor for back-compatibility
                 options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
-            if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
+            }
+
+            if (options.yaxis.tickColor == null) {
+                // grid.tickColor for back-compatibility
                 options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;
-
-            if (options.grid.borderColor == null)
+            }
+
+            if (options.grid.borderColor == null) {
                 options.grid.borderColor = options.grid.color;
-            if (options.grid.tickColor == null)
+            }
+
+            if (options.grid.tickColor == null) {
                 options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();
+            }
 
             // Fill in defaults for axis options, including any unspecified
             // font-spec fields, if a font-spec was provided.
@@ -780,7 +444,6 @@
 
             axisCount = options.xaxes.length || 1;
             for (i = 0; i < axisCount; ++i) {
-
                 axisOptions = options.xaxes[i];
                 if (axisOptions && !axisOptions.tickColor) {
                     axisOptions.tickColor = axisOptions.color;
@@ -802,7 +465,6 @@
 
             axisCount = options.yaxes.length || 1;
             for (i = 0; i < axisCount; ++i) {
-
                 axisOptions = options.yaxes[i];
                 if (axisOptions && !axisOptions.tickColor) {
                     axisOptions.tickColor = axisOptions.color;
@@ -822,66 +484,35 @@
                 }
             }
 
-            // backwards compatibility, to be removed in future
-            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";
-                // Override the inherit to allow the axis to auto-scale
-                if (options.x2axis.min == null) {
-                    options.xaxes[1].min = null;
-                }
-                if (options.x2axis.max == null) {
-                    options.xaxes[1].max = null;
+            // save options on axes for future reference
+            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];
+            }
+
+            //process boxPosition options used for axis.box size
+            $.each(allAxes(), function(_, axis) {
+                axis.boxPosition = axis.options.boxPosition || {centerX: 0, centerY: 0};
+            });
+
+            // add hooks from options
+            for (var n in hooks) {
+                if (options.hooks[n] && options.hooks[n].length) {
+                    hooks[n] = hooks[n].concat(options.hooks[n]);
                 }
             }
-            if (options.y2axis) {
-                options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
-                options.yaxes[1].position = "right";
-                // Override the inherit to allow the axis to auto-scale
-                if (options.y2axis.min == null) {
-                    options.yaxes[1].min = null;
-                }
-                if (options.y2axis.max == null) {
-                    options.yaxes[1].max = null;
-                }
-            }
-            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;
-
-            // save options on axes for future reference
-            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];
-
-            // add hooks from options
-            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) {
+            var oldseries = series;
             series = parseData(d);
             fillInSeriesOptions();
-            processData();
+            processData(oldseries);
         }
 
         function parseData(d) {
@@ -896,9 +527,10 @@
                     $.extend(true, s, d[i]);
 
                     d[i].data = s.data;
+                } else {
+                    s.data = d[i];
                 }
-                else
-                    s.data = d[i];
+
                 res.push(s);
             }
 
@@ -907,51 +539,68 @@
 
         function axisNumber(obj, coord) {
             var a = obj[coord + "axis"];
-            if (typeof a == "object") // if we got a real axis, extract number
+            if (typeof a === "object") {
+                // if we got a real axis, extract number
                 a = a.n;
-            if (typeof a != "number")
+            }
+
+            if (typeof a !== "number") {
                 a = 1; // default to first axis
+            }
+
             return a;
         }
 
         function allAxes() {
             // return flat array without annoying null entries
-            return $.grep(xaxes.concat(yaxes), function (a) { return a; });
+            return xaxes.concat(yaxes).filter(function(a) {
+                return a;
+            });
         }
 
-        function canvasToAxisCoords(pos) {
+        // canvas to axis for cartesian axes
+        function canvasToCartesianAxisCoords(pos) {
             // return an object with x/y corresponding to all used axes
-            var res = {}, i, axis;
+            var res = {},
+                i, axis;
             for (i = 0; i < xaxes.length; ++i) {
                 axis = xaxes[i];
-                if (axis && axis.used)
+                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)
+                if (axis && axis.used) {
                     res["y" + axis.n] = axis.c2p(pos.top);
+                }
             }
 
-            if (res.x1 !== undefined)
+            if (res.x1 !== undefined) {
                 res.x = res.x1;
-            if (res.y1 !== undefined)
+            }
+
+            if (res.y1 !== undefined) {
                 res.y = res.y1;
+            }
 
             return res;
         }
 
-        function axisToCanvasCoords(pos) {
+        // axis to canvas for cartesian axes
+        function cartesianAxisToCanvasCoords(pos) {
             // get canvas coords from the first pair of x/y found in pos
-            var res = {}, i, axis, key;
+            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)
+                    if (pos[key] == null && axis.n === 1) {
                         key = "x";
+                    }
 
                     if (pos[key] != null) {
                         res.left = axis.p2c(pos[key]);
@@ -964,8 +613,9 @@
                 axis = yaxes[i];
                 if (axis && axis.used) {
                     key = "y" + axis.n;
-                    if (pos[key] == null && axis.n == 1)
+                    if (pos[key] == null && axis.n === 1) {
                         key = "y";
+                    }
 
                     if (pos[key] != null) {
                         res.top = axis.p2c(pos[key]);
@@ -978,19 +628,21 @@
         }
 
         function getOrCreateAxis(axes, number) {
-            if (!axes[number - 1])
+            if (!axes[number - 1]) {
                 axes[number - 1] = {
                     n: number, // save the number for future reference
-                    direction: axes == xaxes ? "x" : "y",
-                    options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
+                    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;
+            var neededColors = series.length,
+                maxIndex = -1,
+                i;
 
             // Subtract the number of series that already have fixed colors or
             // color indexes from the number that we still need to generate.
@@ -999,7 +651,7 @@
                 var sc = series[i].color;
                 if (sc != null) {
                     neededColors--;
-                    if (typeof sc == "number" && sc > maxIndex) {
+                    if (typeof sc === "number" && sc > maxIndex) {
                         maxIndex = sc;
                     }
                 }
@@ -1015,12 +667,14 @@
             // Generate all the colors, using first the option colors and then
             // variations on those colors once they're exhausted.
 
-            var c, colors = [], colorPool = options.colors,
-                colorPoolSize = colorPool.length, variation = 0;
+            var c, colors = [],
+                colorPool = options.colors,
+                colorPoolSize = colorPool.length,
+                variation = 0,
+                definedColors = Math.max(0, series.length - neededColors);
 
             for (i = 0; i < neededColors; i++) {
-
-                c = $.color.parse(colorPool[i % colorPoolSize] || "#666");
+                c = $.color.parse(colorPool[(definedColors + i) % colorPoolSize] || "#666");
 
                 // Each time we exhaust the colors in the pool we adjust
                 // a scaling factor used to produce more variations on
@@ -1030,7 +684,7 @@
                 // Reset the variation after every few cycles, or else
                 // it will end up producing only white or black colors.
 
-                if (i % colorPoolSize == 0 && i) {
+                if (i % colorPoolSize === 0 && i) {
                     if (variation >= 0) {
                         if (variation < 0.5) {
                             variation = -variation - 0.2;
@@ -1043,7 +697,8 @@
 
             // Finalize the series options, filling in their colors
 
-            var colori = 0, s;
+            var colori = 0,
+                s;
             for (i = 0; i < series.length; ++i) {
                 s = series[i];
 
@@ -1051,20 +706,23 @@
                 if (s.color == null) {
                     s.color = colors[colori].toString();
                     ++colori;
+                } else if (typeof s.color === "number") {
+                    s.color = colors[s.color].toString();
                 }
-                else if (typeof s.color == "number")
-                    s.color = colors[s.color].toString();
 
                 // turn on lines automatically in case nothing is set
                 if (s.lines.show == null) {
                     var v, show = true;
-                    for (v in s)
+                    for (v in s) {
                         if (s[v] && s[v].show) {
                             show = false;
                             break;
                         }
-                    if (show)
+                    }
+
+                    if (show) {
                         s.lines.show = true;
+                    }
                 }
 
                 // If nothing was provided for lines.zero, default it to match
@@ -1080,33 +738,58 @@
             }
         }
 
-        function processData() {
+        function processData(prevSeries) {
             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,
+                i, j, k, m,
+                s, points, ps, val, f, p,
                 data, format;
 
             function updateAxis(axis, min, max) {
-                if (min < axis.datamin && min != -fakeInfinity)
+                if (min < axis.datamin && min !== -Infinity) {
                     axis.datamin = min;
-                if (max > axis.datamax && max != fakeInfinity)
+                }
+
+                if (max > axis.datamax && max !== Infinity) {
                     axis.datamax = max;
+                }
             }
 
-            $.each(allAxes(), function (_, axis) {
+            function reusePoints(prevSeries, i) {
+                if (prevSeries && prevSeries[i] && prevSeries[i].datapoints && prevSeries[i].datapoints.points) {
+                    return prevSeries[i].datapoints.points;
+                }
+
+                return [];
+            }
+
+            $.each(allAxes(), function(_, axis) {
                 // init axis
-                axis.datamin = topSentry;
-                axis.datamax = bottomSentry;
+                if (axis.options.growOnly !== true) {
+                    axis.datamin = topSentry;
+                    axis.datamax = bottomSentry;
+                } else {
+                    if (axis.datamin === undefined) {
+                        axis.datamin = topSentry;
+                    }
+                    if (axis.datamax === undefined) {
+                        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 ]);
+                s.datapoints = {
+                    points: []
+                };
+
+                if (s.datapoints.points.length === 0) {
+                    s.datapoints.points = reusePoints(prevSeries, i);
+                }
+
+                executeHooks(hooks.processRawData, [s, s.data, s.datapoints]);
             }
 
             // first pass: clean and copy data
@@ -1119,31 +802,50 @@
                 if (!format) {
                     format = [];
                     // find out how to copy
-                    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;
+                    format.push({
+                        x: true,
+                        y: false,
+                        number: true,
+                        required: true,
+                        computeRange: s.xaxis.options.autoScale !== 'none',
+                        defaultValue: null
+                    });
+
+                    format.push({
+                        x: false,
+                        y: true,
+                        number: true,
+                        required: true,
+                        computeRange: s.yaxis.options.autoScale !== 'none',
+                        defaultValue: null
+                    });
+
+                    if (s.stack || s.bars.show || (s.lines.show && s.lines.fill)) {
+                        var expectedPs = s.datapoints.pointsize != null ? s.datapoints.pointsize : (s.data && s.data[0] && s.data[0].length ? s.data[0].length : 3);
+                        if (expectedPs > 2) {
+                            format.push({
+                                x: false,
+                                y: true,
+                                number: true,
+                                required: false,
+                                computeRange: s.yaxis.options.autoScale !== 'none',
+                                defaultValue: 0
+                            });
                         }
                     }
 
                     s.datapoints.format = format;
                 }
 
-                if (s.datapoints.pointsize != null)
-                    continue; // already filled in
+                s.xaxis.used = s.yaxis.used = true;
+
+                if (s.datapoints.pointsize != null) continue; // already filled in
 
                 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];
@@ -1157,20 +859,15 @@
                             if (f) {
                                 if (f.number && val != null) {
                                     val = +val; // convert to number
-                                    if (isNaN(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;
+                                    if (f.required) nullify = true;
+
+                                    if (f.defaultValue != null) val = f.defaultValue;
                                 }
                             }
 
@@ -1184,7 +881,7 @@
                             if (val != null) {
                                 f = format[m];
                                 // extract min/max info
-                                if (f.autoscale !== false) {
+                                if (f.computeRange) {
                                     if (f.x) {
                                         updateAxis(s.xaxis, val, val);
                                     }
@@ -1196,122 +893,62 @@
                             points[k + m] = null;
                         }
                     }
-                    else {
-                        // a little bit of line specific stuff that
-                        // perhaps shouldn't be here, but lacking
-                        // better means...
-                        if (insertSteps && k > 0
-                            && points[k - ps] != null
-                            && points[k - ps] != points[k]
-                            && points[k - ps + 1] != points[k + 1]) {
-                            // copy the point to make room for a middle point
-                            for (m = 0; m < ps; ++m)
-                                points[k + ps + m] = points[k + m];
-
-                            // middle point has same y
-                            points[k + 1] = points[k - ps + 1];
-
-                            // we've added a point, better reflect that
-                            k += ps;
-                        }
-                    }
                 }
+
+                points.length = k; //trims the internal buffer to the correct length
             }
 
             // give the hooks a chance to run
             for (i = 0; i < series.length; ++i) {
                 s = series[i];
 
-                executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
+                executeHooks(hooks.processDatapoints, [s, s.datapoints]);
             }
 
             // second pass: find datamax/datamin for auto-scaling
             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 (format.every(function (f) { return !f.computeRange; })) {
+                    continue;
                 }
 
-                if (s.bars.show) {
-                    // make sure we got room for the bar on the dancing floor
-                    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;
-                    }
+                var range = plot.adjustSeriesDataRange(s,
+                    plot.computeRangeForDataSeries(s));
+
+                executeHooks(hooks.adjustSeriesDataRange, [s, range]);
+
+                updateAxis(s.xaxis, range.xmin, range.xmax);
+                updateAxis(s.yaxis, range.ymin, range.ymax);
+            }
+
+            $.each(allAxes(), function(_, axis) {
+                if (axis.datamin === topSentry) {
+                    axis.datamin = null;
                 }
 
-                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)
+                if (axis.datamax === bottomSentry) {
                     axis.datamax = null;
+                }
             });
         }
 
         function setupCanvases() {
-
             // Make sure the placeholder is clear of everything except canvases
             // from a previous plot in this container that we'll try to re-use.
 
             placeholder.css("padding", 0) // padding messes up the positioning
-                .children().filter(function(){
+                .children().filter(function() {
                     return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base');
                 }).remove();
 
-            if (placeholder.css("position") == 'static')
+            if (placeholder.css("position") === 'static') {
                 placeholder.css("position", "relative"); // for positioning labels and overlay
-
-            surface = new Canvas("flot-base", placeholder);
-            overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features
+            }
+
+            surface = new Canvas("flot-base", placeholder[0]);
+            overlay = new Canvas("flot-overlay", placeholder[0]); // overlay canvas for interactive features
 
             ctx = surface.context;
             octx = overlay.context;
@@ -1333,32 +970,27 @@
         }
 
         function bindEvents() {
-            // bind events
-            if (options.grid.hoverable) {
-                eventHolder.mousemove(onMouseMove);
-
-                // Use bind, rather than .mouseleave, because we officially
-                // still support jQuery 1.2.6, which doesn't define a shortcut
-                // for mouseenter or mouseleave.  This was a bug/oversight that
-                // was fixed somewhere around 1.3.x.  We can return to using
-                // .mouseleave when we drop support for 1.2.6.
-
-                eventHolder.bind("mouseleave", onMouseLeave);
-            }
-
-            if (options.grid.clickable)
-                eventHolder.click(onClick);
-
             executeHooks(hooks.bindEvents, [eventHolder]);
         }
 
+        function addEventHandler(event, handler, eventHolder, priority) {
+            var key = eventHolder + event;
+            var eventList = eventManager[key] || [];
+
+            eventList.push({"event": event, "handler": handler, "eventHolder": eventHolder, "priority": priority});
+            eventList.sort((a, b) => b.priority - a.priority );
+            eventList.forEach( eventData => {
+                eventData.eventHolder.unbind(eventData.event, eventData.handler);
+                eventData.eventHolder.bind(eventData.event, eventData.handler);
+            });
+
+            eventManager[key] = eventList;
+        }
+
         function shutdown() {
-            if (redrawTimeout)
+            if (redrawTimeout) {
                 clearTimeout(redrawTimeout);
-
-            eventHolder.unbind("mousemove", onMouseMove);
-            eventHolder.unbind("mouseleave", onMouseLeave);
-            eventHolder.unbind("click", onClick);
+            }
 
             executeHooks(hooks.shutdown, [eventHolder]);
         }
@@ -1367,54 +999,92 @@
             // set helper functions on the axis, assumes plot area
             // has been computed already
 
-            function identity(x) { return x; }
+            function identity(x) {
+                return x;
+            }
 
             var s, m, t = axis.options.transform || identity,
                 it = axis.options.inverseTransform;
 
             // precompute how much the axis is scaling a point
             // in canvas space
-            if (axis.direction == "x") {
-                s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
+            if (axis.direction === "x") {
+                if (isFinite(t(axis.max) - t(axis.min))) {
+                    s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
+                } else {
+                    s = axis.scale = 1 / Math.abs($.plot.saturated.delta(t(axis.min), t(axis.max), plotWidth));
+                }
                 m = Math.min(t(axis.max), t(axis.min));
-            }
-            else {
-                s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
+            } else {
+                if (isFinite(t(axis.max) - t(axis.min))) {
+                    s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
+                } else {
+                    s = axis.scale = 1 / Math.abs($.plot.saturated.delta(t(axis.min), t(axis.max), plotHeight));
+                }
                 s = -s;
                 m = Math.max(t(axis.max), t(axis.min));
             }
 
             // data point to canvas coordinate
-            if (t == identity) // slight optimization
-                axis.p2c = function (p) { return (p - m) * s; };
-            else
-                axis.p2c = function (p) { return (t(p) - m) * s; };
+            if (t === identity) {
+                // slight optimization
+                axis.p2c = function(p) {
+                    if (isFinite(p - m)) {
+                        return (p - m) * s;
+                    } else {
+                        return (p / 4 - m / 4) * s * 4;
+                    }
+                };
+            } else {
+                axis.p2c = function(p) {
+                    var tp = t(p);
+
+                    if (isFinite(tp - m)) {
+                        return (tp - m) * s;
+                    } else {
+                        return (tp / 4 - m / 4) * s * 4;
+                    }
+                };
+            }
+
             // canvas coordinate to data point
-            if (!it)
-                axis.c2p = function (c) { return m + c / s; };
-            else
-                axis.c2p = function (c) { return it(m + c / 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 || [],
+                ticks = opts.showTickLabels !== 'none' && axis.ticks ? axis.ticks : [],
+                showMajorTickLabels = opts.showTickLabels === 'major' || opts.showTickLabels === 'all',
+                showEndpointsTickLabels = opts.showTickLabels === 'endpoints' || opts.showTickLabels === 'all',
                 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)
+                var label = t.label;
+
+                if (!t.label ||
+                    (showMajorTickLabels === false && i > 0 && i < ticks.length - 1) ||
+                    (showEndpointsTickLabels === false && (i === 0 || i === ticks.length - 1))) {
                     continue;
-
-                var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);
+                }
+
+                if (typeof t.label === 'object') {
+                    label = t.label.name;
+                }
+
+                var info = surface.getTextInfo(layer, label, font);
 
                 labelWidth = Math.max(labelWidth, info.width);
                 labelHeight = Math.max(labelHeight, info.height);
@@ -1431,16 +1101,21 @@
             // dimension per axis, the other dimension depends on the
             // other axes so will have to wait
 
+            // here reserve additional space
+            executeHooks(hooks.axisReserveSpace, [axis]);
+
             var lw = axis.labelWidth,
                 lh = axis.labelHeight,
                 pos = axis.options.position,
                 isXAxis = axis.direction === "x",
                 tickLength = axis.options.tickLength,
+                showTicks = axis.options.showTicks,
+                showMinorTicks = axis.options.showMinorTicks,
+                gridLines = axis.options.gridLines,
                 axisMargin = options.grid.axisMargin,
                 padding = options.grid.labelMargin,
                 innermost = true,
                 outermost = true,
-                first = true,
                 found = false;
 
             // Determine the axis's position in its direction and on its side
@@ -1456,55 +1131,82 @@
                             innermost = false;
                         }
                     }
-                    if (!found) {
-                        first = false;
-                    }
                 }
             });
 
             // The outermost axis on each side has no margin
-
             if (outermost) {
                 axisMargin = 0;
             }
 
-            // The ticks for the first axis in each direction stretch across
-
+            // Set the default tickLength if necessary
             if (tickLength == null) {
-                tickLength = first ? "full" : 5;
+                tickLength = TICK_LENGTH_CONSTANT;
+            }
+
+            // By default, major tick marks are visible
+            if (showTicks == null) {
+                showTicks = true;
+            }
+
+            // By default, minor tick marks are visible
+            if (showMinorTicks == null) {
+                showMinorTicks = true;
             }
 
-            if (!isNaN(+tickLength))
-                padding += +tickLength;
+            // By default, grid lines are visible
+            if (gridLines == null) {
+                if (innermost) {
+                    gridLines = true;
+                } else {
+                    gridLines = false;
+                }
+            }
+
+            if (!isNaN(+tickLength)) {
+                padding += showTicks ? +tickLength : 0;
+            }
 
             if (isXAxis) {
                 lh += padding;
 
-                if (pos == "bottom") {
+                if (pos === "bottom") {
                     plotOffset.bottom += lh + axisMargin;
-                    axis.box = { top: surface.height - plotOffset.bottom, height: lh };
+                    axis.box = {
+                        top: surface.height - plotOffset.bottom,
+                        height: lh
+                    };
+                } else {
+                    axis.box = {
+                        top: plotOffset.top + axisMargin,
+                        height: lh
+                    };
+                    plotOffset.top += lh + axisMargin;
                 }
-                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
+                    };
                 }
             }
-            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 };
-                }
-            }
-
-             // save for future reference
+
+            // save for future reference
             axis.position = pos;
             axis.tickLength = tickLength;
+            axis.showMinorTicks = showMinorTicks;
+            axis.showTicks = showTicks;
+            axis.gridLines = gridLines;
             axis.box.padding = padding;
             axis.innermost = innermost;
         }
@@ -1512,11 +1214,10 @@
         function allocateAxisBoxSecondPhase(axis) {
             // now that all axis boxes have been placed in one
             // dimension, we can set the remaining dimension coordinates
-            if (axis.direction == "x") {
+            if (axis.direction === "x") {
                 axis.box.left = plotOffset.left - axis.labelWidth / 2;
                 axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
-            }
-            else {
+            } else {
                 axis.box.top = plotOffset.top - axis.labelHeight / 2;
                 axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
             }
@@ -1527,28 +1228,30 @@
             // inside the canvas and isn't clipped off
 
             var minMargin = options.grid.minBorderMargin,
-                axis, i;
+                i;
 
             // check stuff from the plot (FIXME: this should just read
             // a value from the series, otherwise it's impossible to
             // customize)
             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));
+                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
-            };
+            var a, offset = {},
+                margins = {
+                    left: minMargin,
+                    right: minMargin,
+                    top: minMargin,
+                    bottom: minMargin
+                };
 
             // check axis labels, note we don't check the actual
             // labels but instead use the overall width/height to not
             // jump as much around with replots
-            $.each(allAxes(), function (_, axis) {
+            $.each(allAxes(), function(_, axis) {
                 if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
                     if (axis.direction === "x") {
                         margins.left = Math.max(margins.left, axis.labelWidth / 2);
@@ -1560,53 +1263,87 @@
                 }
             });
 
+            for (a in margins) {
+                offset[a] = margins[a] - plotOffset[a];
+            }
+            $.each(xaxes.concat(yaxes), function(_, axis) {
+                alignAxisWithGrid(axis, offset, function (offset) {
+                    return offset > 0;
+                });
+            });
+
             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;
+        function alignAxisWithGrid(axis, offset, isValid) {
+            if (axis.direction === "x") {
+                if (axis.position === "bottom" && isValid(offset.bottom)) {
+                    axis.box.top -= Math.ceil(offset.bottom);
+                }
+                if (axis.position === "top" && isValid(offset.top)) {
+                    axis.box.top += Math.ceil(offset.top);
+                }
+            } else {
+                if (axis.position === "left" && isValid(offset.left)) {
+                    axis.box.left += Math.ceil(offset.left);
+                }
+                if (axis.position === "right" && isValid(offset.right)) {
+                    axis.box.left -= Math.ceil(offset.right);
+                }
+            }
+        }
+
+        function setupGrid(autoScale) {
+            var i, a, axes = allAxes(),
+                showGrid = options.grid.show;
 
             // Initialize the plot's offset from the edge of the canvas
 
-            for (var a in plotOffset) {
-                var margin = options.grid.margin || 0;
-                plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
+            for (a in plotOffset) {
+                plotOffset[a] = 0;
             }
 
             executeHooks(hooks.processOffset, [plotOffset]);
 
             // If the grid is visible, add its border width to the offset
-
-            for (var a in plotOffset) {
-                if(typeof(options.grid.borderWidth) == "object") {
+            for (a in plotOffset) {
+                if (typeof (options.grid.borderWidth) === "object") {
                     plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
-                }
-                else {
+                } else {
                     plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
                 }
             }
 
-            $.each(axes, function (_, axis) {
+            $.each(axes, function(_, axis) {
                 var axisOpts = axis.options;
                 axis.show = axisOpts.show == null ? axis.used : axisOpts.show;
                 axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;
-                setRange(axis);
+                setupTickFormatter(axis);
+                executeHooks(hooks.setRange, [axis, autoScale]);
+                setRange(axis, autoScale);
             });
 
             if (showGrid) {
-
-                var allocatedAxes = $.grep(axes, function (axis) {
+                plotWidth = surface.width - plotOffset.left - plotOffset.right;
+                plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
+
+                var allocatedAxes = $.grep(axes, function(axis) {
                     return axis.show || axis.reserveSpace;
                 });
 
-                $.each(allocatedAxes, function (_, axis) {
+                $.each(allocatedAxes, function(_, axis) {
                     // make the ticks
                     setupTickGeneration(axis);
-                    setTicks(axis);
-                    snapRangeToTicks(axis, axis.ticks);
+                    setMajorTicks(axis);
+                    snapRangeToTicks(axis, axis.ticks, series);
+
+                    //for computing the endpoints precision, transformationHelpers are needed
+                    setTransformationHelpers(axis);
+                    setEndpointTicks(axis, series);
+
                     // find labelWidth/Height for axis
                     measureTickLabels(axis);
                 });
@@ -1614,23 +1351,38 @@
                 // with all dimensions calculated, we can compute the
                 // axis bounding boxes, start from the outside
                 // (reverse order)
-                for (i = allocatedAxes.length - 1; i >= 0; --i)
+                for (i = allocatedAxes.length - 1; i >= 0; --i) {
                     allocateAxisBoxFirstPhase(allocatedAxes[i]);
+                }
 
                 // make sure we've got enough space for things that
                 // might stick out
                 adjustLayoutForThingsStickingOut();
 
-                $.each(allocatedAxes, function (_, axis) {
+                $.each(allocatedAxes, function(_, axis) {
                     allocateAxisBoxSecondPhase(axis);
                 });
             }
 
+            //adjust axis and plotOffset according to grid.margins
+            if (options.grid.margin) {
+                for (a in plotOffset) {
+                    var margin = options.grid.margin || 0;
+                    plotOffset[a] += typeof margin === "number" ? margin : (margin[a] || 0);
+                }
+                $.each(xaxes.concat(yaxes), function(_, axis) {
+                    alignAxisWithGrid(axis, options.grid.margin, function(offset) {
+                        return offset !== undefined && offset !== null;
+                    });
+                });
+            }
+
+            //after adjusting the axis, plot width and height will be modified
             plotWidth = surface.width - plotOffset.left - plotOffset.right;
             plotHeight = surface.height - plotOffset.bottom - plotOffset.top;
 
             // now we got the proper plot dimensions, we can compute the scaling
-            $.each(axes, function (_, axis) {
+            $.each(axes, function(_, axis) {
                 setTransformationHelpers(axis);
             });
 
@@ -1638,69 +1390,154 @@
                 drawAxisLabels();
             }
 
-            insertLegend();
+            executeHooks(hooks.setupGrid, []);
         }
 
-        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.0) {
+        function widenMinMax(minimum, maximum) {
+            var min = (minimum === undefined ? null : minimum);
+            var max = (maximum === undefined ? null : maximum);
+            var delta = max - min;
+            if (delta === 0.0) {
                 // degenerate case
-                var widen = max == 0 ? 1 : 0.01;
-
-                if (opts.min == null)
-                    min -= widen;
+                var widen = max === 0 ? 1 : 0.01;
+                var wmin = null;
+                if (min == null) {
+                    wmin -= widen;
+                }
+
                 // always widen max if we couldn't widen min to ensure we
                 // don't fall into min == max which doesn't work
-                if (opts.max == null || opts.min != null)
+                if (max == null || min != null) {
                     max += widen;
-            }
-            else {
-                // consider autoscaling
-                var margin = opts.autoscaleMargin;
-                if (margin != null) {
-                    if (opts.min == null) {
-                        min -= delta * margin;
-                        // make sure we don't go below zero if all values
-                        // are positive
-                        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;
-                    }
+                }
+
+                if (wmin != null) {
+                    min = wmin;
                 }
             }
-            axis.min = min;
-            axis.max = max;
+
+            return {
+                min: min,
+                max: max
+            };
         }
 
-        function setupTickGeneration(axis) {
-            var opts = axis.options;
-
-            // estimate number of ticks
-            var noTicks;
-            if (typeof opts.ticks == "number" && opts.ticks > 0)
-                noTicks = opts.ticks;
-            else
-                // heuristic based on the model a*sqrt(x) fitted to
-                // some data points that seemed reasonable
-                noTicks = 0.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;
+        function autoScaleAxis(axis) {
+            var opts = axis.options,
+                min = opts.min,
+                max = opts.max,
+                datamin = axis.datamin,
+                datamax = axis.datamax,
+                delta;
+
+            switch (opts.autoScale) {
+                case "none":
+                    min = +(opts.min != null ? opts.min : datamin);
+                    max = +(opts.max != null ? opts.max : datamax);
+                    break;
+                case "loose":
+                    if (datamin != null && datamax != null) {
+                        min = datamin;
+                        max = datamax;
+                        delta = $.plot.saturated.saturate(max - min);
+                        var margin = ((typeof opts.autoScaleMargin === 'number') ? opts.autoScaleMargin : 0.02);
+                        min = $.plot.saturated.saturate(min - delta * margin);
+                        max = $.plot.saturated.saturate(max + delta * margin);
+
+                        // make sure we don't go below zero if all values are positive
+                        if (min < 0 && datamin >= 0) {
+                            min = 0;
+                        }
+                    } else {
+                        min = opts.min;
+                        max = opts.max;
+                    }
+                    break;
+                case "exact":
+                    min = (datamin != null ? datamin : opts.min);
+                    max = (datamax != null ? datamax : opts.max);
+                    break;
+                case "sliding-window":
+                    if (datamax > max) {
+                        // move the window to fit the new data,
+                        // keeping the axis range constant
+                        max = datamax;
+                        min = Math.max(datamax - (opts.windowSize || 100), min);
+                    }
+                    break;
+            }
+
+            var widenedMinMax = widenMinMax(min, max);
+            min = widenedMinMax.min;
+            max = widenedMinMax.max;
+
+            // grow loose or grow exact supported
+            if (opts.growOnly === true && opts.autoScale !== "none" && opts.autoScale !== "sliding-window") {
+                min = (min < datamin) ? min : (datamin !== null ? datamin : min);
+                max = (max > datamax) ? max : (datamax !== null ? datamax : max);
             }
 
-            var magn = Math.pow(10, -dec),
+            axis.autoScaledMin = min;
+            axis.autoScaledMax = max;
+        }
+
+        function setRange(axis, autoScale) {
+            var min = typeof axis.options.min === 'number' ? axis.options.min : axis.min,
+                max = typeof axis.options.max === 'number' ? axis.options.max : axis.max,
+                plotOffset = axis.options.offset;
+
+            if (autoScale) {
+                autoScaleAxis(axis);
+                min = axis.autoScaledMin;
+                max = axis.autoScaledMax;
+            }
+
+            min = (min != null ? min : -1) + (plotOffset.below || 0);
+            max = (max != null ? max : 1) + (plotOffset.above || 0);
+
+            if (min > max) {
+                var tmp = min;
+                min = max;
+                max = tmp;
+                axis.options.offset = { above: 0, below: 0 };
+            }
+
+            axis.min = $.plot.saturated.saturate(min);
+            axis.max = $.plot.saturated.saturate(max);
+        }
+
+        function computeValuePrecision (min, max, direction, ticks, tickDecimals) {
+            var noTicks = fixupNumberOfTicks(direction, surface, ticks);
+
+            var delta = $.plot.saturated.delta(min, max, noTicks),
+                dec = -Math.floor(Math.log(delta) / Math.LN10);
+
+            //if it is called with tickDecimals, then the precision should not be greather then that
+            if (tickDecimals && dec > tickDecimals) {
+                dec = tickDecimals;
+            }
+
+            var magn = parseFloat('1e' + (-dec)),
+                norm = delta / magn;
+
+            if (norm > 2.25 && norm < 3 && (dec + 1) <= tickDecimals) {
+                //we need an extra decimals when tickSize is 2.5
+                ++dec;
+            }
+
+            return isFinite(dec) ? dec : 0;
+        };
+
+        function computeTickSize (min, max, noTicks, tickDecimals) {
+            var delta = $.plot.saturated.delta(min, max, noTicks),
+                dec = -Math.floor(Math.log(delta) / Math.LN10);
+
+            //if it is called with tickDecimals, then the precision should not be greather then that
+            if (tickDecimals && dec > tickDecimals) {
+                dec = tickDecimals;
+            }
+
+            var magn = parseFloat('1e' + (-dec)),
                 norm = delta / magn, // norm is between 1.0 and 10.0
                 size;
 
@@ -1708,10 +1545,8 @@
                 size = 1;
             } else if (norm < 3) {
                 size = 2;
-                // special case for 2.5, requires an extra decimal
-                if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
+                if (norm > 2.25 && (tickDecimals == null || (dec + 1) <= tickDecimals)) {
                     size = 2.5;
-                    ++dec;
                 }
             } else if (norm < 7.5) {
                 size = 5;
@@ -1720,82 +1555,97 @@
             }
 
             size *= magn;
-
-            if (opts.minTickSize != null && size < opts.minTickSize) {
-                size = opts.minTickSize;
+            return size;
+        }
+
+        function getAxisTickSize(min, max, direction, options, tickDecimals) {
+            var noTicks;
+
+            if (typeof options.ticks === "number" && options.ticks > 0) {
+                noTicks = options.ticks;
+            } else {
+            // heuristic based on the model a*sqrt(x) fitted to
+            // some data points that seemed reasonable
+                noTicks = 0.3 * Math.sqrt(direction === "x" ? surface.width : surface.height);
+            }
+
+            var size = computeTickSize(min, max, noTicks, tickDecimals);
+
+            if (options.minTickSize != null && size < options.minTickSize) {
+                size = options.minTickSize;
             }
 
-            axis.delta = delta;
-            axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
-            axis.tickSize = opts.tickSize || size;
-
-            // Time mode was moved to a plug-in in 0.8, and since so many people use it
-            // we'll add an especially friendly reminder to make sure they included it.
-
-            if (opts.mode == "time" && !axis.tickGenerator) {
-                throw new Error("Time mode requires the flot.time plugin.");
+            return options.tickSize || size;
+        };
+
+        function fixupNumberOfTicks(direction, surface, ticksOption) {
+            var noTicks;
+
+            if (typeof ticksOption === "number" && ticksOption > 0) {
+                noTicks = ticksOption;
+            } else {
+                noTicks = 0.3 * Math.sqrt(direction === "x" ? surface.width : surface.height);
             }
 
+            return noTicks;
+        }
+
+        function setupTickFormatter(axis) {
+            var opts = axis.options;
+            if (!axis.tickFormatter) {
+                if (typeof opts.tickFormatter === 'function') {
+                    axis.tickFormatter = function() {
+                        var args = Array.prototype.slice.call(arguments);
+                        return "" + opts.tickFormatter.apply(null, args);
+                    };
+                } else {
+                    axis.tickFormatter = defaultTickFormatter;
+                }
+            }
+        }
+
+        function setupTickGeneration(axis) {
+            var opts = axis.options;
+            var noTicks;
+
+            noTicks = fixupNumberOfTicks(axis.direction, surface, opts.ticks);
+
+            axis.delta = $.plot.saturated.delta(axis.min, axis.max, noTicks);
+            var precision = plot.computeValuePrecision(axis.min, axis.max, axis.direction, noTicks, opts.tickDecimals);
+
+            axis.tickDecimals = Math.max(0, opts.tickDecimals != null ? opts.tickDecimals : precision);
+            axis.tickSize = getAxisTickSize(axis.min, axis.max, axis.direction, opts, opts.tickDecimals);
+
             // Flot supports base-10 axes; any other mode else is handled by a plug-in,
             // like flot.time.js.
 
             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 tickDecimals was specified, ensure that we have exactly that
-					// much precision; otherwise default to the value's own precision.
-
-					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 (typeof opts.tickGenerator === 'function') {
+                    axis.tickGenerator = opts.tickGenerator;
+                } else {
+                    axis.tickGenerator = defaultTickGenerator;
+                }
             }
 
-            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 otherAxis = (axis.direction === "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
+                if (otherAxis && otherAxis.used && otherAxis !== axis) {
                     // consider snapping min/max to outermost nice ticks
-                    var niceTicks = axis.tickGenerator(axis);
+                    var niceTicks = axis.tickGenerator(axis, plot);
                     if (niceTicks.length > 0) {
-                        if (opts.min == null)
+                        if (opts.min == null) {
                             axis.min = Math.min(axis.min, niceTicks[0]);
-                        if (opts.max == null && niceTicks.length > 1)
+                        }
+
+                        if (opts.max == null && niceTicks.length > 1) {
                             axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
+                        }
                     }
 
-                    axis.tickGenerator = function (axis) {
+                    axis.tickGenerator = function(axis) {
                         // copy ticks, scaled to this axis
-                        var ticks = [], v, i;
+                        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);
@@ -1808,28 +1658,31 @@
                     // ticks don't necessarily fit naturally
                     if (!axis.mode && opts.tickDecimals == null) {
                         var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
-                            ts = axis.tickGenerator(axis);
+                            ts = axis.tickGenerator(axis, plot);
 
                         // only proceed if the tick interval rounded
                         // with an extra decimal doesn't give us a
                         // zero at end
-                        if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
+                        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))
-                    // generate the ticks
+        function setMajorTicks(axis) {
+            var oticks = axis.options.ticks,
+                ticks = [];
+            if (oticks == null || (typeof oticks === "number" && oticks > 0)) {
+                ticks = axis.tickGenerator(axis, plot);
+            } else if (oticks) {
+                if ($.isFunction(oticks)) {
+                // generate the ticks
                     ticks = oticks(axis);
-                else
+                } else {
                     ticks = oticks;
+                }
             }
 
             // clean up/labelify the supplied ticks, copy them over
@@ -1838,48 +1691,105 @@
             for (i = 0; i < ticks.length; ++i) {
                 var label = null;
                 var t = ticks[i];
-                if (typeof t == "object") {
+                if (typeof t === "object") {
                     v = +t[0];
-                    if (t.length > 1)
+                    if (t.length > 1) {
                         label = t[1];
+                    }
+                } else {
+                    v = +t;
                 }
-                else
-                    v = +t;
-                if (label == null)
-                    label = axis.tickFormatter(v, axis);
-                if (!isNaN(v))
-                    axis.ticks.push({ v: v, label: label });
+
+                if (!isNaN(v)) {
+                    axis.ticks.push(
+                        newTick(v, label, axis, 'major'));
+                }
             }
         }
 
-        function snapRangeToTicks(axis, ticks) {
-            if (axis.options.autoscaleMargin && ticks.length > 0) {
+        function newTick(v, label, axis, type) {
+            if (label === null) {
+                switch (type) {
+                    case 'min':
+                    case 'max':
+                        //improving the precision of endpoints
+                        var precision = getEndpointPrecision(v, axis);
+                        label = isFinite(precision) ? axis.tickFormatter(v, axis, precision, plot) : axis.tickFormatter(v, axis, precision, plot);
+                        break;
+                    case 'major':
+                        label = axis.tickFormatter(v, axis, undefined, plot);
+                }
+            }
+            return {
+                v: v,
+                label: label
+            };
+        }
+
+        function snapRangeToTicks(axis, ticks, series) {
+            var anyDataInSeries = function(series) {
+                return series.some(e => e.datapoints.points.length > 0);
+            }
+
+            if (axis.options.autoScale === "loose" && ticks.length > 0 && anyDataInSeries(series)) {
                 // snap to ticks
-                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);
+                axis.min = Math.min(axis.min, ticks[0].v);
+                axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
+            }
+        }
+
+        function getEndpointPrecision(value, axis) {
+            var canvas1 = Math.floor(axis.p2c(value)),
+                canvas2 = axis.direction === "x" ? canvas1 + 1 : canvas1 - 1,
+                point1 = axis.c2p(canvas1),
+                point2 = axis.c2p(canvas2),
+                precision = computeValuePrecision(point1, point2, axis.direction, 1);
+
+            return precision;
+        }
+
+        function setEndpointTicks(axis, series) {
+            if (isValidEndpointTick(axis, series)) {
+                axis.ticks.unshift(newTick(axis.min, null, axis, 'min'));
+                axis.ticks.push(newTick(axis.max, null, axis, 'max'));
+            }
+        }
+
+        function isValidEndpointTick(axis, series) {
+            if (axis.options.showTickLabels === 'endpoints') {
+                return true;
+            }
+            if (axis.options.showTickLabels === 'all') {
+                var associatedSeries = series.filter(function(s) {
+                        return s.xaxis === axis;
+                    }),
+                    notAllBarSeries = associatedSeries.some(function(s) {
+                        return !s.bars.show;
+                    });
+                return associatedSeries.length === 0 || notAllBarSeries;
+            }
+            if (axis.options.showTickLabels === 'major' || axis.options.showTickLabels === 'none') {
+                return false;
             }
         }
 
         function draw() {
-
             surface.clear();
-
             executeHooks(hooks.drawBackground, [ctx]);
 
             var grid = options.grid;
 
             // draw background, if any
-            if (grid.show && grid.backgroundColor)
+            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]]);
+                executeHooks(hooks.drawSeries, [ctx, series[i], i, getColorOrGradient]);
                 drawSeries(series[i]);
             }
 
@@ -1893,7 +1803,6 @@
 
             // A draw implies that either the axes or data have changed, so we
             // should probably update the overlay highlights as well.
-
             triggerRedrawOverlay();
         }
 
@@ -1902,10 +1811,13 @@
 
             for (var i = 0; i < axes.length; ++i) {
                 axis = axes[i];
-                if (axis.direction == coord) {
+                if (axis.direction === coord) {
                     key = coord + axis.n + "axis";
-                    if (!ranges[key] && axis.n == 1)
-                        key = coord + "axis"; // support x1axis as xaxis
+                    if (!ranges[key] && axis.n === 1) {
+                        // support x1axis as xaxis
+                        key = coord + "axis";
+                    }
+
                     if (ranges[key]) {
                         from = ranges[key].from;
                         to = ranges[key].to;
@@ -1916,7 +1828,7 @@
 
             // backwards-compat stuff - to be removed in future
             if (!ranges[key]) {
-                axis = coord == "x" ? xaxes[0] : yaxes[0];
+                axis = coord === "x" ? xaxes[0] : yaxes[0];
                 from = ranges[coord + "1"];
                 to = ranges[coord + "2"];
             }
@@ -1928,7 +1840,11 @@
                 to = tmp;
             }
 
-            return { from: from, to: to, axis: axis };
+            return {
+                from: from,
+                to: to,
+                axis: axis
+            };
         }
 
         function drawBackground() {
@@ -1940,14 +1856,11 @@
             ctx.restore();
         }
 
-        function drawGrid() {
-            var i, axes, bw, bc;
-
-            ctx.save();
-            ctx.translate(plotOffset.left, plotOffset.top);
-
+        function drawMarkings() {
             // draw markings
-            var markings = options.grid.markings;
+            var markings = options.grid.markings,
+                axes;
+
             if (markings) {
                 if ($.isFunction(markings)) {
                     axes = plot.getAxes();
@@ -1961,25 +1874,34 @@
                     markings = markings(axes);
                 }
 
+                var i;
                 for (i = 0; i < markings.length; ++i) {
                     var m = markings[i],
                         xrange = extractRange(m, "x"),
                         yrange = extractRange(m, "y");
 
                     // fill in missing
-                    if (xrange.from == null)
+                    if (xrange.from == null) {
                         xrange.from = xrange.axis.min;
-                    if (xrange.to == null)
+                    }
+
+                    if (xrange.to == null) {
                         xrange.to = xrange.axis.max;
-                    if (yrange.from == null)
+                    }
+
+                    if (yrange.from == null) {
                         yrange.from = yrange.axis.min;
-                    if (yrange.to == null)
+                    }
+
+                    if (yrange.to == null) {
                         yrange.to = yrange.axis.max;
+                    }
 
                     // clip
                     if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
-                        yrange.to < yrange.axis.min || yrange.from > yrange.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);
@@ -2010,183 +1932,369 @@
                             ctx.lineTo(xrange.to + subPixel, yrange.to);
                         } else {
                             ctx.moveTo(xrange.from, yrange.to + subPixel);
-                            ctx.lineTo(xrange.to, yrange.to + subPixel);                            
+                            ctx.lineTo(xrange.to, yrange.to + subPixel);
                         }
                         ctx.stroke();
                     } else {
                         ctx.fillStyle = m.color || options.grid.markingsColor;
                         ctx.fillRect(xrange.from, yrange.to,
-                                     xrange.to - xrange.from,
-                                     yrange.from - yrange.to);
+                            xrange.to - xrange.from,
+                            yrange.from - yrange.to);
                     }
                 }
             }
-
-            // draw the ticks
-            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;
-
-                // find the edges
-                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);
+        }
+
+        function findEdges(axis) {
+            var box = axis.box,
+                x = 0,
+                y = 0;
+
+            // find the edges
+            if (axis.direction === "x") {
+                x = 0;
+                y = box.top - plotOffset.top + (axis.position === "top" ? box.height : 0);
+            } else {
+                y = 0;
+                x = box.left - plotOffset.left + (axis.position === "left" ? box.width : 0) + axis.boxPosition.centerX;
+            }
+
+            return {
+                x: x,
+                y: y
+            };
+        };
+
+        function alignPosition(lineWidth, pos) {
+            return ((lineWidth % 2) !== 0) ? Math.floor(pos) + 0.5 : pos;
+        };
+
+        function drawTickBar(axis) {
+            ctx.lineWidth = 1;
+            var edges = findEdges(axis),
+                x = edges.x,
+                y = edges.y;
+
+            // draw tick bar
+            if (axis.show) {
+                var xoff = 0,
+                    yoff = 0;
+
+                ctx.strokeStyle = axis.options.color;
+                ctx.beginPath();
+                if (axis.direction === "x") {
+                    xoff = plotWidth + 1;
+                } else {
+                    yoff = plotHeight + 1;
                 }
-                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.direction === "x") {
+                    y = alignPosition(ctx.lineWidth, y);
+                } else {
+                    x = alignPosition(ctx.lineWidth, x);
                 }
 
-                // draw tick bar
-                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) + 0.5;
-                        } else {
-                            x = Math.floor(x) + 0.5;
+                ctx.moveTo(x, y);
+                ctx.lineTo(x + xoff, y + yoff);
+                ctx.stroke();
+            }
+        };
+
+        function drawTickMarks(axis) {
+            var t = axis.tickLength,
+                minorTicks = axis.showMinorTicks,
+                minorTicksNr = MINOR_TICKS_COUNT_CONSTANT,
+                edges = findEdges(axis),
+                x = edges.x,
+                y = edges.y,
+                i = 0;
+
+            // draw major tick marks
+            ctx.strokeStyle = axis.options.color;
+            ctx.beginPath();
+
+            for (i = 0; i < axis.ticks.length; ++i) {
+                var v = axis.ticks[i].v,
+                    xoff = 0,
+                    yoff = 0,
+                    xminor = 0,
+                    yminor = 0,
+                    j;
+
+                if (!isNaN(v) && v >= axis.min && v <= axis.max) {
+                    if (axis.direction === "x") {
+                        x = axis.p2c(v);
+                        yoff = t;
+
+                        if (axis.position === "top") {
+                            yoff = -yoff;
+                        }
+                    } else {
+                        y = axis.p2c(v);
+                        xoff = t;
+
+                        if (axis.position === "left") {
+                            xoff = -xoff;
                         }
                     }
 
-                    ctx.moveTo(x, y);
-                    ctx.lineTo(x + xoff, y + yoff);
-                    ctx.stroke();
-                }
-
-                // draw ticks
-
-                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
-                        // skip those lying on the axes if we got a border
-                        || (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) + 0.5;
-                        else
-                            y = Math.floor(y) + 0.5;
+                    if (axis.direction === "x") {
+                        x = alignPosition(ctx.lineWidth, x);
+                    } else {
+                        y = alignPosition(ctx.lineWidth, y);
                     }
 
                     ctx.moveTo(x, y);
                     ctx.lineTo(x + xoff, y + yoff);
                 }
 
-                ctx.stroke();
+                //draw minor tick marks
+                if (minorTicks === true && i < axis.ticks.length - 1) {
+                    var v1 = axis.ticks[i].v,
+                        v2 = axis.ticks[i + 1].v,
+                        step = (v2 - v1) / (minorTicksNr + 1);
+
+                    for (j = 1; j <= minorTicksNr; j++) {
+                        // compute minor tick position
+                        if (axis.direction === "x") {
+                            yminor = t / 2; // minor ticks are half length
+                            x = alignPosition(ctx.lineWidth, axis.p2c(v1 + j * step))
+
+                            if (axis.position === "top") {
+                                yminor = -yminor;
+                            }
+
+                            // don't go over the plot borders
+                            if ((x < 0) || (x > plotWidth)) {
+                                continue;
+                            }
+                        } else {
+                            xminor = t / 2; // minor ticks are half length
+                            y = alignPosition(ctx.lineWidth, axis.p2c(v1 + j * step));
+
+                            if (axis.position === "left") {
+                                xminor = -xminor;
+                            }
+
+                            // don't go over the plot borders
+                            if ((y < 0) || (y > plotHeight)) {
+                                continue;
+                            }
+                        }
+
+                        ctx.moveTo(x, y);
+                        ctx.lineTo(x + xminor, y + yminor);
+                    }
+                }
+            }
+
+            ctx.stroke();
+        };
+
+        function drawGridLines(axis) {
+            // check if the line will be overlapped with a border
+            var overlappedWithBorder = function (value) {
+                var bw = options.grid.borderWidth;
+                return (((typeof bw === "object" && bw[axis.position] > 0) || bw > 0) && (value === axis.min || value === axis.max));
+            };
+
+            ctx.strokeStyle = options.grid.tickColor;
+            ctx.beginPath();
+            var i;
+            for (i = 0; i < axis.ticks.length; ++i) {
+                var v = axis.ticks[i].v,
+                    xoff = 0,
+                    yoff = 0,
+                    x = 0,
+                    y = 0;
+
+                if (isNaN(v) || v < axis.min || v > axis.max) continue;
+
+                // skip those lying on the axes if we got a border
+                if (overlappedWithBorder(v)) continue;
+
+                if (axis.direction === "x") {
+                    x = axis.p2c(v);
+                    y = plotHeight;
+                    yoff = -plotHeight;
+                } else {
+                    x = 0;
+                    y = axis.p2c(v);
+                    xoff = plotWidth;
+                }
+
+                if (axis.direction === "x") {
+                    x = alignPosition(ctx.lineWidth, x);
+                } else {
+                    y = alignPosition(ctx.lineWidth, y);
+                }
+
+                ctx.moveTo(x, y);
+                ctx.lineTo(x + xoff, y + yoff);
             }
 
+            ctx.stroke();
+        };
+
+        function drawBorder() {
+            // If either borderWidth or borderColor is an object, then draw the border
+            // line by line instead of as one rectangle
+            var bw = options.grid.borderWidth,
+                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);
+            }
+        };
+
+        function drawGrid() {
+            var axes, bw;
+
+            ctx.save();
+            ctx.translate(plotOffset.left, plotOffset.top);
+
+            drawMarkings();
+
+            axes = allAxes();
+            bw = options.grid.borderWidth;
+
+            for (var j = 0; j < axes.length; ++j) {
+                var axis = axes[j];
+
+                if (!axis.show) {
+                    continue;
+                }
+
+                drawTickBar(axis);
+                if (axis.showTicks === true) {
+                    drawTickMarks(axis);
+                }
+
+                if (axis.gridLines === true) {
+                    drawGridLines(axis, bw);
+                }
+            }
 
             // draw border
             if (bw) {
-                // If either borderWidth or borderColor is an object, then draw the border
-                // line by line instead of as one rectangle
-                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);
-                }
+                drawBorder();
             }
 
             ctx.restore();
         }
 
         function drawAxisLabels() {
-
-            $.each(allAxes(), function (_, axis) {
+            $.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;
+                    i, x, y, halign, valign, info,
+                    margin = 3,
+                    nullBox = {x: NaN, y: NaN, width: NaN, height: NaN}, newLabelBox, labelBoxes = [],
+                    overlapping = function(x11, y11, x12, y12, x21, y21, x22, y22) {
+                        return ((x11 <= x21 && x21 <= x12) || (x21 <= x11 && x11 <= x22)) &&
+                               ((y11 <= y21 && y21 <= y12) || (y21 <= y11 && y11 <= y22));
+                    },
+                    overlapsOtherLabels = function(newLabelBox, previousLabelBoxes) {
+                        return previousLabelBoxes.some(function(labelBox) {
+                            return overlapping(
+                                newLabelBox.x, newLabelBox.y, newLabelBox.x + newLabelBox.width, newLabelBox.y + newLabelBox.height,
+                                labelBox.x, labelBox.y, labelBox.x + labelBox.width, labelBox.y + labelBox.height);
+                        });
+                    },
+                    drawAxisLabel = function (tick, labelBoxes) {
+                        if (!tick || !tick.label || tick.v < axis.min || tick.v > axis.max) {
+                            return nullBox;
+                        }
+
+                        info = surface.getTextInfo(layer, tick.label, font);
+
+                        if (axis.direction === "x") {
+                            halign = "center";
+                            x = plotOffset.left + axis.p2c(tick.v);
+                            if (axis.position === "bottom") {
+                                y = box.top + box.padding - axis.boxPosition.centerY;
+                            } else {
+                                y = box.top + box.height - box.padding + axis.boxPosition.centerY;
+                                valign = "bottom";
+                            }
+                            newLabelBox = {x: x - info.width / 2 - margin, y: y - margin, width: info.width + 2 * margin, height: info.height + 2 * margin};
+                        } else {
+                            valign = "middle";
+                            y = plotOffset.top + axis.p2c(tick.v);
+                            if (axis.position === "left") {
+                                x = box.left + box.width - box.padding - axis.boxPosition.centerX;
+                                halign = "right";
+                            } else {
+                                x = box.left + box.padding + axis.boxPosition.centerX;
+                            }
+                            newLabelBox = {x: x - info.width / 2 - margin, y: y - margin, width: info.width + 2 * margin, height: info.height + 2 * margin};
+                        }
+
+                        if (overlapsOtherLabels(newLabelBox, labelBoxes)) {
+                            return nullBox;
+                        }
+
+                        surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
+
+                        return newLabelBox;
+                    };
 
                 // Remove text before checking for axis.show and ticks.length;
                 // otherwise plugins, like flot-tickrotor, that draw their own
@@ -2194,928 +2302,439 @@
 
                 surface.removeText(layer);
 
-                if (!axis.show || axis.ticks.length == 0)
+                executeHooks(hooks.drawAxis, [axis, surface]);
+
+                if (!axis.show) {
                     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";
+                }
+
+                switch (axis.options.showTickLabels) {
+                    case 'none':
+                        break;
+                    case 'endpoints':
+                        labelBoxes.push(drawAxisLabel(axis.ticks[0], labelBoxes));
+                        labelBoxes.push(drawAxisLabel(axis.ticks[axis.ticks.length - 1], labelBoxes));
+                        break;
+                    case 'major':
+                        labelBoxes.push(drawAxisLabel(axis.ticks[0], labelBoxes));
+                        labelBoxes.push(drawAxisLabel(axis.ticks[axis.ticks.length - 1], labelBoxes));
+                        for (i = 1; i < axis.ticks.length - 1; ++i) {
+                            labelBoxes.push(drawAxisLabel(axis.ticks[i], labelBoxes));
                         }
-                    } 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;
+                        break;
+                    case 'all':
+                        labelBoxes.push(drawAxisLabel(axis.ticks[0], []));
+                        labelBoxes.push(drawAxisLabel(axis.ticks[axis.ticks.length - 1], labelBoxes));
+                        for (i = 1; i < axis.ticks.length - 1; ++i) {
+                            labelBoxes.push(drawAxisLabel(axis.ticks[i], labelBoxes));
                         }
-                    }
-
-                    surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
+                        break;
                 }
             });
         }
 
         function drawSeries(series) {
-            if (series.lines.show)
-                drawSeriesLines(series);
-            if (series.bars.show)
-                drawSeriesBars(series);
-            if (series.points.show)
-                drawSeriesPoints(series);
+            if (series.lines.show) {
+                $.plot.drawSeries.drawSeriesLines(series, ctx, plotOffset, plotWidth, plotHeight, plot.drawSymbol, getColorOrGradient);
+            }
+
+            if (series.bars.show) {
+                $.plot.drawSeries.drawSeriesBars(series, ctx, plotOffset, plotWidth, plotHeight, plot.drawSymbol, getColorOrGradient);
+            }
+
+            if (series.points.show) {
+                $.plot.drawSeries.drawSeriesPoints(series, ctx, plotOffset, plotWidth, plotHeight, plot.drawSymbol, getColorOrGradient);
+            }
         }
 
-        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)
+        function computeRangeForDataSeries(series, force, isValid) {
+            var points = series.datapoints.points,
+                ps = series.datapoints.pointsize,
+                format = series.datapoints.format,
+                topSentry = Number.POSITIVE_INFINITY,
+                bottomSentry = Number.NEGATIVE_INFINITY,
+                range = {
+                    xmin: topSentry,
+                    ymin: topSentry,
+                    xmax: bottomSentry,
+                    ymax: bottomSentry
+                };
+
+            for (var j = 0; j < points.length; j += ps) {
+                if (points[j] === null) {
+                    continue;
+                }
+
+                if (typeof (isValid) === 'function' && !isValid(points[j])) {
+                    continue;
+                }
+
+                for (var m = 0; m < ps; ++m) {
+                    var val = points[j + m],
+                        f = format[m];
+                    if (f === null || f === undefined) {
                         continue;
-
-                    // clip with ymin
-                    if (y1 <= y2 && y1 < axisy.min) {
-                        if (y2 < axisy.min)
-                            continue;   // line segment is outside
-                        // compute new intersection point
-                        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;
-                    }
-
-                    // clip with ymax
-                    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;
-                    }
-
-                    // clip with xmin
-                    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 (typeof (isValid) === 'function' && !isValid(val)) {
+                        continue;
                     }
 
-                    // clip with xmax
-                    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 ((!force && !f.computeRange) || val === Infinity || val === -Infinity) {
+                        continue;
                     }
 
-                    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;
-
-                // we process each segment in two turns, first forward
-                // direction to sketch out top, then once we hit the
-                // end we go backwards to sketch the bottom
-                while (true) {
-                    if (ps > 0 && i > points.length + ps)
-                        break;
-
-                    i += ps; // ps is negative if going backwards
-
-                    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) {
-                            // at turning point
-                            segmentEnd = i;
-                            ps = -ps;
-                            ypos = 2;
-                            continue;
+                    if (f.x === true) {
+                        if (val < range.xmin) {
+                            range.xmin = val;
                         }
 
-                        if (ps < 0 && i == segmentStart + ps) {
-                            // done with the reverse sweep
-                            ctx.fill();
-                            areaOpen = false;
-                            ps = -ps;
-                            ypos = 1;
-                            i = segmentStart = segmentEnd + ps;
-                            continue;
+                        if (val > range.xmax) {
+                            range.xmax = val;
                         }
                     }
 
-                    if (x1 == null || x2 == null)
-                        continue;
-
-                    // clip x values
-
-                    // clip with xmin
-                    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;
-                    }
-
-                    // clip with xmax
-                    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) {
-                        // open area
-                        ctx.beginPath();
-                        ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
-                        areaOpen = true;
-                    }
-
-                    // now first check the case where both is outside
-                    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;
-                    }
-
-                    // else it's a bit more complicated, there might
-                    // be a flat maxed out rectangle first, then a
-                    // triangular cutout or reverse; to find these
-                    // keep track of the current x values
-                    var x1old = x1, x2old = x2;
-
-                    // clip the y values, without shortcutting, we
-                    // go through all cases in turn
-
-                    // clip with ymin
-                    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;
-                    }
-
-                    // clip with ymax
-                    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 the x value was changed we got a rectangle
-                    // to fill
-                    if (x1 != x1old) {
-                        ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
-                        // it goes to (x1, y1), but we fill that below
-                    }
-
-                    // fill triangular section, this sometimes result
-                    // in redundant points if (x1, y1) hasn't changed
-                    // from previous line to, but we just ignore that
-                    ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
-                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
-
-                    // fill the other rectangle if it's there
-                    if (x2 != x2old) {
-                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
-                        ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
+                    if (f.y === true) {
+                        if (val < range.ymin) {
+                            range.ymin = val;
+                        }
+
+                        if (val > range.ymax) {
+                            range.ymax = val;
+                        }
                     }
                 }
             }
 
-            ctx.save();
-            ctx.translate(plotOffset.left, plotOffset.top);
-            ctx.lineJoin = "round";
-
-            var lw = series.lines.lineWidth,
-                sw = series.shadowSize;
-            // FIXME: consider another form of shadow when filling is turned on
-            if (lw > 0 && sw > 0) {
-                // draw shadow as a thick and thin line with transparency
-                ctx.lineWidth = sw;
-                ctx.strokeStyle = "rgba(0,0,0,0.1)";
-                // position shadow at angle from the mid of line
-                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);
+            return range;
+        };
+
+        function adjustSeriesDataRange(series, range) {
+            if (series.bars.show) {
+                // make sure we got room for the bar on the dancing floor
+                var delta;
+
+                // update bar width if needed
+                var useAbsoluteBarWidth = series.bars.barWidth[1];
+                if (series.datapoints && series.datapoints.points && !useAbsoluteBarWidth) {
+                    computeBarWidth(series);
+                }
+
+                var barWidth = series.bars.barWidth[0] || series.bars.barWidth;
+                switch (series.bars.align) {
+                    case "left":
+                        delta = 0;
+                        break;
+                    case "right":
+                        delta = -barWidth;
+                        break;
+                    default:
+                        delta = -barWidth / 2;
+                }
+
+                if (series.bars.horizontal) {
+                    range.ymin += delta;
+                    range.ymax += delta + barWidth;
+                }
+                else {
+                    range.xmin += delta;
+                    range.xmax += delta + barWidth;
+                }
             }
 
-            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();
+            if ((series.bars.show && series.bars.zero) || (series.lines.show && series.lines.zero)) {
+                var ps = series.datapoints.pointsize;
+
+                // make sure the 0 point is included in the computed y range when requested
+                if (ps <= 2) {
+                    /*if ps > 0 the points were already taken into account for autoScale */
+                    range.ymin = Math.min(0, range.ymin);
+                    range.ymax = Math.max(0, range.ymax);
+                }
+            }
+
+            return range;
+        };
+
+        function computeBarWidth(series) {
+            var xValues = [];
+            var pointsize = series.datapoints.pointsize, minDistance = Number.MAX_VALUE;
+
+            if (series.datapoints.points.length <= pointsize) {
+                minDistance = 1;
+            }
+
+            var start = series.bars.horizontal ? 1 : 0;
+            for (var j = start; j < series.datapoints.points.length; j += pointsize) {
+                if (isFinite(series.datapoints.points[j]) && series.datapoints.points[j] !== null) {
+                    xValues.push(series.datapoints.points[j]);
+                }
+            }
+
+            function onlyUnique(value, index, self) {
+                return self.indexOf(value) === index;
+            }
+
+            xValues = xValues.filter( onlyUnique );
+            xValues.sort(function(a, b){return a - b});
+
+            for (var j = 1; j < xValues.length; j++) {
+                var distance = Math.abs(xValues[j] - xValues[j - 1]);
+                if (distance < minDistance && isFinite(distance)) {
+                    minDistance = distance;
                 }
             }
 
-            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 the user sets the line width to 0, we change it to a very 
-            // small value. A line width of 0 seems to force the default of 1.
-            // Doing the conditional here allows the shadow setting to still be 
-            // optional even with a lineWidth of 0.
-
-            if( lw == 0 )
-                lw = 0.0001;
-
-            if (lw > 0 && sw > 0) {
-                // draw shadow in two steps
-                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);
+            if (typeof series.bars.barWidth === "number") {
+                series.bars.barWidth = series.bars.barWidth * minDistance;
+            } else {
+                series.bars.barWidth[0] = series.bars.barWidth[0] * minDistance;
             }
-
-            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;
-
-            // in horizontal mode, we start the bar from the left
-            // instead of from the bottom so it appears to be
-            // horizontal rather than vertical
-            if (horizontal) {
-                drawBottom = drawRight = drawTop = true;
-                drawLeft = false;
-                left = b;
-                right = x;
-                top = y + barLeft;
-                bottom = y + barRight;
-
-                // account for negative bars
-                if (right < left) {
-                    tmp = right;
-                    right = left;
-                    left = tmp;
-                    drawLeft = true;
-                    drawRight = false;
+        // returns the data item the mouse is over/ the cursor is closest to, or null if none is found
+        function findNearbyItem(mouseX, mouseY, seriesFilter, radius, computeDistance) {
+            var i, j,
+                item = null,
+                smallestDistance = radius * radius + 1;
+
+            for (var i = series.length - 1; i >= 0; --i) {
+                if (!seriesFilter(i)) continue;
+
+                var s = series[i];
+                if (!s.datapoints) return;
+
+                if (s.lines.show || s.points.show) {
+                    var found = findNearbyPoint(s, mouseX, mouseY, radius, smallestDistance, computeDistance);
+                    if (found) {
+                        smallestDistance = found.distance;
+                        item = [i, found.dataIndex];
+                    }
                 }
-            }
-            else {
-                drawLeft = drawRight = drawTop = true;
-                drawBottom = false;
-                left = x + barLeft;
-                right = x + barRight;
-                bottom = b;
-                top = y;
-
-                // account for negative bars
-                if (top < bottom) {
-                    tmp = top;
-                    top = bottom;
-                    bottom = tmp;
-                    drawBottom = true;
-                    drawTop = false;
+
+                if (s.bars.show && !item) { // no other point can be nearby
+                    var foundIndex = findNearbyBar(s, mouseX, mouseY);
+                    if (foundIndex >= 0) item = [i, foundIndex];
                 }
             }
 
-            // clip
-            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 (item) {
+                i = item[0];
+                j = item[1];
+                var ps = series[i].datapoints.pointsize;
+
+                return {
+                    datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
+                    dataIndex: j,
+                    series: series[i],
+                    seriesIndex: i
+                };
             }
 
-            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);
-
-            // fill the bar
-            if (fillStyleCallback) {
-                c.fillStyle = fillStyleCallback(bottom, top);
-                c.fillRect(left, top, right - left, bottom - top)
+            return null;
+        }
+
+        function findNearbyPoint (series, mouseX, mouseY, maxDistance, smallestDistance, computeDistance) {
+            var mx = series.xaxis.c2p(mouseX),
+                my = series.yaxis.c2p(mouseY),
+                maxx = maxDistance / series.xaxis.scale,
+                maxy = maxDistance / series.yaxis.scale,
+                points = series.datapoints.points,
+                ps = series.datapoints.pointsize;
+
+            // with inverse transforms, we can't use the maxx/maxy
+            // optimization, sadly
+            if (series.xaxis.options.inverseTransform) {
+                maxx = Number.MAX_VALUE;
             }
 
-            // draw outline
-            if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
-                c.beginPath();
-
-                // FIXME: inline moveTo is buggy with excanvas
-                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();
+            if (series.yaxis.options.inverseTransform) {
+                maxy = Number.MAX_VALUE;
             }
-        }
-
-        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);
+
+            var found = null;
+            for (var j = 0; j < points.length; j += ps) {
+                var x = points[j];
+                var y = points[j + 1];
+                if (x == null) {
+                    continue;
+                }
+
+                if (x - mx > maxx || x - mx < -maxx ||
+                    y - my > maxy || y - my < -maxy) {
+                    continue;
+                }
+
+                // We have to calculate distances in pixels, not in
+                // data units, because the scales of the axes may be different
+                var dx = Math.abs(series.xaxis.p2c(x) - mouseX);
+                var dy = Math.abs(series.yaxis.p2c(y) - mouseY);
+                var dist = computeDistance ? computeDistance(dx, dy) : dx * dx + dy * dy;
+
+                // use <= to ensure last point takes precedence
+                // (last generally means on top of)
+                if (dist < smallestDistance) {
+                    smallestDistance = dist;
+                    found = { dataIndex: j / ps, distance: dist };
                 }
             }
 
-            ctx.save();
-            ctx.translate(plotOffset.left, plotOffset.top);
-
-            // FIXME: figure out a way to add shadows (for instance along the right edge)
-            ctx.lineWidth = series.bars.lineWidth;
-            ctx.strokeStyle = series.color;
-
-            var barLeft;
+            return found;
+        }
+
+        function findNearbyBar (series, mouseX, mouseY) {
+            var barLeft, barRight,
+                barWidth = series.bars.barWidth[0] || series.bars.barWidth,
+                mx = series.xaxis.c2p(mouseX),
+                my = series.yaxis.c2p(mouseY),
+                points = series.datapoints.points,
+                ps = series.datapoints.pointsize;
 
             switch (series.bars.align) {
                 case "left":
                     barLeft = 0;
                     break;
                 case "right":
-                    barLeft = -series.bars.barWidth;
+                    barLeft = -barWidth;
                     break;
                 default:
-                    barLeft = -series.bars.barWidth / 2;
+                    barLeft = -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 : 0.4;
-            c.normalize();
-            return c.toString();
+            barRight = barLeft + barWidth;
+
+            var fillTowards = series.bars.fillTowards || 0;
+            var bottom = fillTowards > series.yaxis.min ? Math.min(series.yaxis.max, fillTowards) : series.yaxis.min;
+
+            var foundIndex = -1;
+            for (var j = 0; j < points.length; j += ps) {
+                var x = points[j], y = points[j + 1];
+                if (x == null)
+                    continue;
+
+                // for a bar graph, the cursor must be inside the bar
+                if (series.bars.horizontal ?
+                    (mx <= Math.max(bottom, x) && mx >= Math.min(bottom, x) &&
+                        my >= y + barLeft && my <= y + barRight) :
+                    (mx >= x + barLeft && mx <= x + barRight &&
+                        my >= Math.min(bottom, y) && my <= Math.max(bottom, y)))
+                        foundIndex = j / ps;
+            }
+
+            return foundIndex;
         }
 
-        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;
-
-            // Build a list of legend entries, with each having a label and a color
-
-            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
-                        });
+        function findNearbyInterpolationPoint(posX, posY, seriesFilter) {
+            var i, j, dist, dx, dy, ps,
+                item,
+                smallestDistance = Number.MAX_VALUE;
+
+            for (i = 0; i < series.length; ++i) {
+                if (!seriesFilter(i)) {
+                    continue;
+                }
+                var points = series[i].datapoints.points;
+                ps = series[i].datapoints.pointsize;
+
+                // if the data is coming from positive -> negative, reverse the comparison
+                const comparer = points[points.length - ps] < points[0]
+                    ? function (x1, x2) { return x1 > x2 }
+                    : function (x1, x2) { return x2 > x1 };
+
+                // do not interpolate outside the bounds of the data.
+                if (comparer(posX, points[0])) {
+                    continue;
+                }
+
+                // Find the nearest points, x-wise
+                for (j = ps; j < points.length; j += ps) {
+                    if (comparer(posX, points[j])) {
+                        break;
                     }
                 }
-            }
-
-            // Sort the legend using either the default or a custom comparator
-
-            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   // Logical XOR
-                        );
-                    });
-                }
-            }
-
-            // Generate markup for the list of entries, in their final order
-
-            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.0) {
-                    // put in the transparent background
-                    // separately to avoid blended labels and
-                    // label boxes
-                    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);
+
+                // Now Interpolate
+                var y,
+                    p1x = points[j - ps],
+                    p1y = points[j - ps + 1],
+                    p2x = points[j],
+                    p2y = points[j + 1];
+
+                if ((p1x === undefined) || (p2x === undefined) ||
+                    (p1y === undefined) || (p2y === undefined)) {
+                    continue;
                 }
-            }
-        }
-
-
-        // interactive features
-
-        var highlights = [],
-            redrawTimeout = null;
-
-        // returns the data item the mouse is over, or null if none is found
-        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), // precompute some stuff to make the loop faster
-                    my = axisy.c2p(mouseY),
-                    maxx = maxDistance / axisx.scale,
-                    maxy = maxDistance / axisy.scale;
-
-                ps = s.datapoints.pointsize;
-                // with inverse transforms, we can't use the maxx/maxy
-                // optimization, sadly
-                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;
-
-                        // For points and lines, the cursor must be within a
-                        // certain distance to the data point
-                        if (x - mx > maxx || x - mx < -maxx ||
-                            y - my > maxy || y - my < -maxy)
-                            continue;
-
-                        // We have to calculate distances in pixels, not in
-                        // data units, because the scales of the axes may be different
-                        var dx = Math.abs(axisx.p2c(x) - mouseX),
-                            dy = Math.abs(axisy.p2c(y) - mouseY),
-                            dist = dx * dx + dy * dy; // we save the sqrt
-
-                        // use <= to ensure last point takes precedence
-                        // (last generally means on top of)
-                        if (dist < smallestDistance) {
-                            smallestDistance = dist;
-                            item = [i, j / ps];
-                        }
-                    }
+
+                if (p1x === p2x) {
+                    y = p2y
+                } else {
+                    y = p1y + (p2y - p1y) * (posX - p1x) / (p2x - p1x);
                 }
 
-                if (s.bars.show && !item) { // no other point can be nearby
-
-                    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;
-
-                        // for a bar graph, the cursor must be inside the bar
-                        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];
-                    }
+                posY = y;
+
+                dx = Math.abs(series[i].xaxis.p2c(p2x) - posX);
+                dy = Math.abs(series[i].yaxis.p2c(p2y) - posY);
+                dist = dx * dx + dy * dy;
+
+                if (dist < smallestDistance) {
+                    smallestDistance = dist;
+                    item = [posX, posY, i, j];
                 }
             }
 
             if (item) {
-                i = item[0];
-                j = item[1];
+                i = item[2];
+                j = item[3];
                 ps = series[i].datapoints.pointsize;
-
-                return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
-                         dataIndex: j,
-                         series: series[i],
-                         seriesIndex: i };
+                points = series[i].datapoints.points;
+                p1x = points[j - ps];
+                p1y = points[j - ps + 1];
+                p2x = points[j];
+                p2y = points[j + 1];
+
+                return {
+                    datapoint: [item[0], item[1]],
+                    leftPoint: [p1x, p1y],
+                    rightPoint: [p2x, p2y],
+                    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; });
-        }
-
-        // trigger click or hover event (they send the same parameters
-        // so we share their code)
-        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) {
-                // fill in mouse pos for any listeners out there
-                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) {
-                // clear auto-highlights
-                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) {      // skip event queue
+            if (t === -1) { // skip event queue
                 drawOverlay();
                 return;
             }
 
-            if (!redrawTimeout)
-                redrawTimeout = setTimeout(drawOverlay, t);
-        }
-
-        function drawOverlay() {
-            redrawTimeout = null;
-
-            // draw highlights
-            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);
+            if (!redrawTimeout) {
+                redrawTimeout = setTimeout(function() {
+                    drawOverlay(plot);
+                }, t);
             }
-            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();
+        function drawOverlay(plot) {
+            redrawTimeout = null;
+
+            if (!octx) {
                 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', 0.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', 0.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);
+            overlay.clear();
+            executeHooks(hooks.drawOverlay, [octx, overlay]);
+            var event = new CustomEvent('onDrawingDone');
+            plot.getEventHolder().dispatchEvent(event);
+            plot.getPlaceholder().trigger('drawingdone');
         }
 
         function getColorOrGradient(spec, bottom, top, defaultColor) {
-            if (typeof spec == "string")
+            if (typeof spec === "string") {
                 return spec;
-            else {
+            } else {
                 // assume this is a gradient spec; IE currently only
                 // supports a simple vertical gradient properly, so that's
                 // what we support too
@@ -3123,12 +2742,16 @@
 
                 for (var i = 0, l = spec.colors.length; i < l; ++i) {
                     var c = spec.colors[i];
-                    if (typeof c != "string") {
+                    if (typeof c !== "string") {
                         var co = $.color.parse(defaultColor);
-                        if (c.brightness != null)
+                        if (c.brightness != null) {
                             co = co.scale('rgb', c.brightness);
-                        if (c.opacity != null)
+                        }
+
+                        if (c.opacity != null) {
                             co.a *= c.opacity;
+                        }
+
                         c = co.toString();
                     }
                     gradient.addColorStop(i / (l - 1), c);
@@ -3142,27 +2765,22 @@
     // Add the plot function to the top level of the jQuery object
 
     $.plot = function(placeholder, data, options) {
-        //var t0 = new Date();
         var plot = new Plot($(placeholder), data, options, $.plot.plugins);
-        //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
         return plot;
     };
 
-    $.plot.version = "0.8.3";
+    $.plot.version = "3.0.0";
 
     $.plot.plugins = [];
 
     // Also add the plot function as a chainable property
-
     $.fn.plot = function(data, options) {
         return this.each(function() {
             $.plot(this, data, options);
         });
     };
 
-    // round to nearby lower multiple of base
-    function floorInBase(n, base) {
-        return base * Math.floor(n / base);
-    }
-
+    $.plot.linearTickGenerator = defaultTickGenerator;
+    $.plot.defaultTickFormatter = defaultTickFormatter;
+    $.plot.expRepTickFormatter = expRepTickFormatter;
 })(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.legend.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,437 @@
+/* Flot plugin for drawing legends.
+
+*/
+
+(function($) {
+    var defaultOptions = {
+        legend: {
+            show: false,
+            noColumns: 1,
+            labelFormatter: null, // fn: string -> string
+            container: null, // container (as jQuery object) to put legend in, null means default on top of graph
+            position: 'ne', // position of default legend container within plot
+            margin: 5, // distance from grid edge to default legend container within plot
+            sorted: null // default to no legend sorting
+        }
+    };
+
+    function insertLegend(plot, options, placeholder, legendEntries) {
+        // clear before redraw
+        if (options.legend.container != null) {
+            $(options.legend.container).html('');
+        } else {
+            placeholder.find('.legend').remove();
+        }
+
+        if (!options.legend.show) {
+            return;
+        }
+
+        // Save the legend entries in legend options
+        var entries = options.legend.legendEntries = legendEntries,
+            plotOffset = options.legend.plotOffset = plot.getPlotOffset(),
+            html = [],
+            entry, labelHtml, iconHtml,
+            j = 0,
+            i,
+            pos = "",
+            p = options.legend.position,
+            m = options.legend.margin,
+            shape = {
+                name: '',
+                label: '',
+                xPos: '',
+                yPos: ''
+            };
+
+        html[j++] = '<svg class="legendLayer" style="width:inherit;height:inherit;">';
+        html[j++] = '<rect class="background" width="100%" height="100%"/>';
+        html[j++] = svgShapeDefs;
+
+        var left = 0;
+        var columnWidths = [];
+        var style = window.getComputedStyle(document.querySelector('body'));
+        for (i = 0; i < entries.length; ++i) {
+            var columnIndex = i % options.legend.noColumns;
+            entry = entries[i];
+            shape.label = entry.label;
+            var info = plot.getSurface().getTextInfo('', shape.label, {
+                style: style.fontStyle,
+                variant: style.fontVariant,
+                weight: style.fontWeight,
+                size: parseInt(style.fontSize),
+                lineHeight: parseInt(style.lineHeight),
+                family: style.fontFamily
+            });
+
+            var labelWidth = info.width;
+            // 36px = 1.5em + 6px margin
+            var iconWidth = 48;
+            if (columnWidths[columnIndex]) {
+                if (labelWidth > columnWidths[columnIndex]) {
+                    columnWidths[columnIndex] = labelWidth + iconWidth;
+                }
+            } else {
+                columnWidths[columnIndex] = labelWidth + iconWidth;
+            }
+        }
+
+        // Generate html for icons and labels from a list of entries
+        for (i = 0; i < entries.length; ++i) {
+            var columnIndex = i % options.legend.noColumns;
+            entry = entries[i];
+            iconHtml = '';
+            shape.label = entry.label;
+            shape.xPos = (left + 3) + 'px';
+            left += columnWidths[columnIndex];
+            if ((i + 1) % options.legend.noColumns === 0) {
+                left = 0;
+            }
+            shape.yPos = Math.floor(i / options.legend.noColumns) * 1.5 + 'em';
+            // area
+            if (entry.options.lines.show && entry.options.lines.fill) {
+                shape.name = 'area';
+                shape.fillColor = entry.color;
+                iconHtml += getEntryIconHtml(shape);
+            }
+            // bars
+            if (entry.options.bars.show) {
+                shape.name = 'bar';
+                shape.fillColor = entry.color;
+                iconHtml += getEntryIconHtml(shape);
+            }
+            // lines
+            if (entry.options.lines.show && !entry.options.lines.fill) {
+                shape.name = 'line';
+                shape.strokeColor = entry.color;
+                shape.strokeWidth = entry.options.lines.lineWidth;
+                iconHtml += getEntryIconHtml(shape);
+            }
+            // points
+            if (entry.options.points.show) {
+                shape.name = entry.options.points.symbol;
+                shape.strokeColor = entry.color;
+                shape.fillColor = entry.options.points.fillColor;
+                shape.strokeWidth = entry.options.points.lineWidth;
+                iconHtml += getEntryIconHtml(shape);
+            }
+
+            labelHtml = '<text x="' + shape.xPos + '" y="' + shape.yPos + '" text-anchor="start"><tspan dx="2em" dy="1.2em">' + shape.label + '</tspan></text>'
+            html[j++] = '<g>' + iconHtml + labelHtml + '</g>';
+        }
+
+        html[j++] = '</svg>';
+        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 width = 6;
+        for (i = 0; i < columnWidths.length; ++i) {
+            width += columnWidths[i];
+        }
+
+        var legendEl,
+            height = Math.ceil(entries.length / options.legend.noColumns) * 1.6;
+        if (!options.legend.container) {
+            legendEl = $('<div class="legend" style="position:absolute;' + pos + '">' + html.join('') + '</div>').appendTo(placeholder);
+            legendEl.css('width', width + 'px');
+            legendEl.css('height', height + 'em');
+            legendEl.css('pointerEvents', 'none');
+        } else {
+            legendEl = $(html.join('')).appendTo(options.legend.container)[0];
+            options.legend.container.style.width = width + 'px';
+            options.legend.container.style.height = height + 'em';
+        }
+    }
+
+    // Generate html for a shape
+    function getEntryIconHtml(shape) {
+        var html = '',
+            name = shape.name,
+            x = shape.xPos,
+            y = shape.yPos,
+            fill = shape.fillColor,
+            stroke = shape.strokeColor,
+            width = shape.strokeWidth;
+        switch (name) {
+            case 'circle':
+                html = '<use xlink:href="#circle" class="legendIcon" ' +
+                    'x="' + x + '" ' +
+                    'y="' + y + '" ' +
+                    'fill="' + fill + '" ' +
+                    'stroke="' + stroke + '" ' +
+                    'stroke-width="' + width + '" ' +
+                    'width="1.5em" height="1.5em"' +
+                    '/>';
+                break;
+            case 'diamond':
+                html = '<use xlink:href="#diamond" class="legendIcon" ' +
+                    'x="' + x + '" ' +
+                    'y="' + y + '" ' +
+                    'fill="' + fill + '" ' +
+                    'stroke="' + stroke + '" ' +
+                    'stroke-width="' + width + '" ' +
+                    'width="1.5em" height="1.5em"' +
+                    '/>';
+                break;
+            case 'cross':
+                html = '<use xlink:href="#cross" class="legendIcon" ' +
+                    'x="' + x + '" ' +
+                    'y="' + y + '" ' +
+                    // 'fill="' + fill + '" ' +
+                    'stroke="' + stroke + '" ' +
+                    'stroke-width="' + width + '" ' +
+                    'width="1.5em" height="1.5em"' +
+                    '/>';
+                break;
+            case 'rectangle':
+                html = '<use xlink:href="#rectangle" class="legendIcon" ' +
+                    'x="' + x + '" ' +
+                    'y="' + y + '" ' +
+                    'fill="' + fill + '" ' +
+                    'stroke="' + stroke + '" ' +
+                    'stroke-width="' + width + '" ' +
+                    'width="1.5em" height="1.5em"' +
+                    '/>';
+                break;
+            case 'plus':
+                html = '<use xlink:href="#plus" class="legendIcon" ' +
+                    'x="' + x + '" ' +
+                    'y="' + y + '" ' +
+                    // 'fill="' + fill + '" ' +
+                    'stroke="' + stroke + '" ' +
+                    'stroke-width="' + width + '" ' +
+                    'width="1.5em" height="1.5em"' +
+                    '/>';
+                break;
+            case 'bar':
+                html = '<use xlink:href="#bars" class="legendIcon" ' +
+                    'x="' + x + '" ' +
+                    'y="' + y + '" ' +
+                    'fill="' + fill + '" ' +
+                    // 'stroke="' + stroke + '" ' +
+                    // 'stroke-width="' + width + '" ' +
+                    'width="1.5em" height="1.5em"' +
+                    '/>';
+                break;
+            case 'area':
+                html = '<use xlink:href="#area" class="legendIcon" ' +
+                    'x="' + x + '" ' +
+                    'y="' + y + '" ' +
+                    'fill="' + fill + '" ' +
+                    // 'stroke="' + stroke + '" ' +
+                    // 'stroke-width="' + width + '" ' +
+                    'width="1.5em" height="1.5em"' +
+                    '/>';
+                break;
+            case 'line':
+                html = '<use xlink:href="#line" class="legendIcon" ' +
+                    'x="' + x + '" ' +
+                    'y="' + y + '" ' +
+                    // 'fill="' + fill + '" ' +
+                    'stroke="' + stroke + '" ' +
+                    'stroke-width="' + width + '" ' +
+                    'width="1.5em" height="1.5em"' +
+                    '/>';
+                break;
+            default:
+                // default is circle
+                html = '<use xlink:href="#circle" class="legendIcon" ' +
+                    'x="' + x + '" ' +
+                    'y="' + y + '" ' +
+                    'fill="' + fill + '" ' +
+                    'stroke="' + stroke + '" ' +
+                    'stroke-width="' + width + '" ' +
+                    'width="1.5em" height="1.5em"' +
+                    '/>';
+        }
+
+        return html;
+    }
+
+    // Define svg symbols for shapes
+    var svgShapeDefs = '' +
+        '<defs>' +
+            '<symbol id="line" fill="none" viewBox="-5 -5 25 25">' +
+                '<polyline points="0,15 5,5 10,10 15,0"/>' +
+            '</symbol>' +
+
+            '<symbol id="area" stroke-width="1" viewBox="-5 -5 25 25">' +
+                '<polyline points="0,15 5,5 10,10 15,0, 15,15, 0,15"/>' +
+            '</symbol>' +
+
+            '<symbol id="bars" stroke-width="1" viewBox="-5 -5 25 25">' +
+                '<polyline points="1.5,15.5 1.5,12.5, 4.5,12.5 4.5,15.5 6.5,15.5 6.5,3.5, 9.5,3.5 9.5,15.5 11.5,15.5 11.5,7.5 14.5,7.5 14.5,15.5 1.5,15.5"/>' +
+            '</symbol>' +
+
+            '<symbol id="circle" viewBox="-5 -5 25 25">' +
+                '<circle cx="0" cy="15" r="2.5"/>' +
+                '<circle cx="5" cy="5" r="2.5"/>' +
+                '<circle cx="10" cy="10" r="2.5"/>' +
+                '<circle cx="15" cy="0" r="2.5"/>' +
+            '</symbol>' +
+
+            '<symbol id="rectangle" viewBox="-5 -5 25 25">' +
+                '<rect x="-2.1" y="12.9" width="4.2" height="4.2"/>' +
+                '<rect x="2.9" y="2.9" width="4.2" height="4.2"/>' +
+                '<rect x="7.9" y="7.9" width="4.2" height="4.2"/>' +
+                '<rect x="12.9" y="-2.1" width="4.2" height="4.2"/>' +
+            '</symbol>' +
+
+            '<symbol id="diamond" viewBox="-5 -5 25 25">' +
+                '<path d="M-3,15 L0,12 L3,15, L0,18 Z"/>' +
+                '<path d="M2,5 L5,2 L8,5, L5,8 Z"/>' +
+                '<path d="M7,10 L10,7 L13,10, L10,13 Z"/>' +
+                '<path d="M12,0 L15,-3 L18,0, L15,3 Z"/>' +
+            '</symbol>' +
+
+            '<symbol id="cross" fill="none" viewBox="-5 -5 25 25">' +
+                '<path d="M-2.1,12.9 L2.1,17.1, M2.1,12.9 L-2.1,17.1 Z"/>' +
+                '<path d="M2.9,2.9 L7.1,7.1 M7.1,2.9 L2.9,7.1 Z"/>' +
+                '<path d="M7.9,7.9 L12.1,12.1 M12.1,7.9 L7.9,12.1 Z"/>' +
+                '<path d="M12.9,-2.1 L17.1,2.1 M17.1,-2.1 L12.9,2.1 Z"/>' +
+            '</symbol>' +
+
+            '<symbol id="plus" fill="none" viewBox="-5 -5 25 25">' +
+                '<path d="M0,12 L0,18, M-3,15 L3,15 Z"/>' +
+                '<path d="M5,2 L5,8 M2,5 L8,5 Z"/>' +
+                '<path d="M10,7 L10,13 M7,10 L13,10 Z"/>' +
+                '<path d="M15,-3 L15,3 M12,0 L18,0 Z"/>' +
+            '</symbol>' +
+        '</defs>';
+
+    // Generate a list of legend entries in their final order
+    function getLegendEntries(series, labelFormatter, sorted) {
+        var lf = labelFormatter,
+            legendEntries = series.reduce(function(validEntries, s, i) {
+                var labelEval = (lf ? lf(s.label, s) : s.label)
+                if (s.hasOwnProperty("label") ? labelEval : true) {
+                    var entry = {
+                        label: labelEval || 'Plot ' + (i + 1),
+                        color: s.color,
+                        options: {
+                            lines: s.lines,
+                            points: s.points,
+                            bars: s.bars
+                        }
+                    }
+                    validEntries.push(entry)
+                }
+                return validEntries;
+            }, []);
+
+        // Sort the legend using either the default or a custom comparator
+        if (sorted) {
+            if ($.isFunction(sorted)) {
+                legendEntries.sort(sorted);
+            } else if (sorted === 'reverse') {
+                legendEntries.reverse();
+            } else {
+                var ascending = (sorted !== 'descending');
+                legendEntries.sort(function(a, b) {
+                    return a.label === b.label
+                        ? 0
+                        : ((a.label < b.label) !== ascending ? 1 : -1 // Logical XOR
+                        );
+                });
+            }
+        }
+
+        return legendEntries;
+    }
+
+    // return false if opts1 same as opts2
+    function checkOptions(opts1, opts2) {
+        for (var prop in opts1) {
+            if (opts1.hasOwnProperty(prop)) {
+                if (opts1[prop] !== opts2[prop]) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    // Compare two lists of legend entries
+    function shouldRedraw(oldEntries, newEntries) {
+        if (!oldEntries || !newEntries) {
+            return true;
+        }
+
+        if (oldEntries.length !== newEntries.length) {
+            return true;
+        }
+        var i, newEntry, oldEntry, newOpts, oldOpts;
+        for (i = 0; i < newEntries.length; i++) {
+            newEntry = newEntries[i];
+            oldEntry = oldEntries[i];
+
+            if (newEntry.label !== oldEntry.label) {
+                return true;
+            }
+
+            if (newEntry.color !== oldEntry.color) {
+                return true;
+            }
+
+            // check for changes in lines options
+            newOpts = newEntry.options.lines;
+            oldOpts = oldEntry.options.lines;
+            if (checkOptions(newOpts, oldOpts)) {
+                return true;
+            }
+
+            // check for changes in points options
+            newOpts = newEntry.options.points;
+            oldOpts = oldEntry.options.points;
+            if (checkOptions(newOpts, oldOpts)) {
+                return true;
+            }
+
+            // check for changes in bars options
+            newOpts = newEntry.options.bars;
+            oldOpts = oldEntry.options.bars;
+            if (checkOptions(newOpts, oldOpts)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    function init(plot) {
+        plot.hooks.setupGrid.push(function (plot) {
+            var options = plot.getOptions();
+            var series = plot.getData(),
+                labelFormatter = options.legend.labelFormatter,
+                oldEntries = options.legend.legendEntries,
+                oldPlotOffset = options.legend.plotOffset,
+                newEntries = getLegendEntries(series, labelFormatter, options.legend.sorted),
+                newPlotOffset = plot.getPlotOffset();
+
+            if (shouldRedraw(oldEntries, newEntries) ||
+                checkOptions(oldPlotOffset, newPlotOffset)) {
+                insertLegend(plot, options, plot.getPlaceholder(), newEntries);
+            }
+        });
+    }
+
+    $.plot.plugins.push({
+        init: init,
+        options: defaultOptions,
+        name: 'legend',
+        version: '1.0'
+    });
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.legend.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(e){function t(t,l,n,i){if(null!=l.legend.container?e(l.legend.container).html(""):n.find(".legend").remove(),l.legend.show){var r,a,h,d,c=l.legend.legendEntries=i,g=l.legend.plotOffset=t.getPlotOffset(),p=[],f=0,m="",u=l.legend.position,b=l.legend.margin,y={name:"",label:"",xPos:"",yPos:""};p[f++]='<svg class="legendLayer" style="width:inherit;height:inherit;">',p[f++]='<rect class="background" width="100%" height="100%"/>',p[f++]=s;var x=0,w=[],k=window.getComputedStyle(document.querySelector("body"));for(d=0;d<c.length;++d){L=d%l.legend.noColumns;r=c[d],y.label=r.label;var v=t.getSurface().getTextInfo("",y.label,{style:k.fontStyle,variant:k.fontVariant,weight:k.fontWeight,size:parseInt(k.fontSize),lineHeight:parseInt(k.lineHeight),family:k.fontFamily}).width;w[L]?v>w[L]&&(w[L]=v+48):w[L]=v+48}for(d=0;d<c.length;++d){var L=d%l.legend.noColumns;r=c[d],h="",y.label=r.label,y.xPos=x+3+"px",x+=w[L],(d+1)%l.legend.noColumns==0&&(x=0),y.yPos=1.5*Math.floor(d/l.legend.noColumns)+"em",r.options.lines.show&&r.options.lines.fill&&(y.name="area",y.fillColor=r.color,h+=o(y)),r.options.bars.show&&(y.name="bar",y.fillColor=r.color,h+=o(y)),r.options.lines.show&&!r.options.lines.fill&&(y.name="line",y.strokeColor=r.color,y.strokeWidth=r.options.lines.lineWidth,h+=o(y)),r.options.points.show&&(y.name=r.options.points.symbol,y.strokeColor=r.color,y.fillColor=r.options.points.fillColor,y.strokeWidth=r.options.points.lineWidth,h+=o(y)),a='<text x="'+y.xPos+'" y="'+y.yPos+'" text-anchor="start"><tspan dx="2em" dy="1.2em">'+y.label+"</tspan></text>",p[f++]="<g>"+h+a+"</g>"}p[f++]="</svg>",null==b[0]&&(b=[b,b]),"n"===u.charAt(0)?m+="top:"+(b[1]+g.top)+"px;":"s"===u.charAt(0)&&(m+="bottom:"+(b[1]+g.bottom)+"px;"),"e"===u.charAt(1)?m+="right:"+(b[0]+g.right)+"px;":"w"===u.charAt(1)&&(m+="left:"+(b[0]+g.left)+"px;");var M=6;for(d=0;d<w.length;++d)M+=w[d];var C,P=1.6*Math.ceil(c.length/l.legend.noColumns);l.legend.container?(C=e(p.join("")).appendTo(l.legend.container)[0],l.legend.container.style.width=M+"px",l.legend.container.style.height=P+"em"):((C=e('<div class="legend" style="position:absolute;'+m+'">'+p.join("")+"</div>").appendTo(n)).css("width",M+"px"),C.css("height",P+"em"),C.css("pointerEvents","none"))}}function o(e){var t="",o=e.name,l=e.xPos,n=e.yPos,i=e.fillColor,s=e.strokeColor,r=e.strokeWidth;switch(o){case"circle":t='<use xlink:href="#circle" class="legendIcon" x="'+l+'" y="'+n+'" fill="'+i+'" stroke="'+s+'" stroke-width="'+r+'" width="1.5em" height="1.5em"/>';break;case"diamond":t='<use xlink:href="#diamond" class="legendIcon" x="'+l+'" y="'+n+'" fill="'+i+'" stroke="'+s+'" stroke-width="'+r+'" width="1.5em" height="1.5em"/>';break;case"cross":t='<use xlink:href="#cross" class="legendIcon" x="'+l+'" y="'+n+'" stroke="'+s+'" stroke-width="'+r+'" width="1.5em" height="1.5em"/>';break;case"rectangle":t='<use xlink:href="#rectangle" class="legendIcon" x="'+l+'" y="'+n+'" fill="'+i+'" stroke="'+s+'" stroke-width="'+r+'" width="1.5em" height="1.5em"/>';break;case"plus":t='<use xlink:href="#plus" class="legendIcon" x="'+l+'" y="'+n+'" stroke="'+s+'" stroke-width="'+r+'" width="1.5em" height="1.5em"/>';break;case"bar":t='<use xlink:href="#bars" class="legendIcon" x="'+l+'" y="'+n+'" fill="'+i+'" width="1.5em" height="1.5em"/>';break;case"area":t='<use xlink:href="#area" class="legendIcon" x="'+l+'" y="'+n+'" fill="'+i+'" width="1.5em" height="1.5em"/>';break;case"line":t='<use xlink:href="#line" class="legendIcon" x="'+l+'" y="'+n+'" stroke="'+s+'" stroke-width="'+r+'" width="1.5em" height="1.5em"/>';break;default:t='<use xlink:href="#circle" class="legendIcon" x="'+l+'" y="'+n+'" fill="'+i+'" stroke="'+s+'" stroke-width="'+r+'" width="1.5em" height="1.5em"/>'}return t}function l(t,o,l){var n=o,i=t.reduce(function(e,t,o){var l=n?n(t.label,t):t.label;if(!t.hasOwnProperty("label")||l){var i={label:l||"Plot "+(o+1),color:t.color,options:{lines:t.lines,points:t.points,bars:t.bars}};e.push(i)}return e},[]);if(l)if(e.isFunction(l))i.sort(l);else if("reverse"===l)i.reverse();else{var s="descending"!==l;i.sort(function(e,t){return e.label===t.label?0:e.label<t.label!==s?1:-1})}return i}function n(e,t){for(var o in e)if(e.hasOwnProperty(o)&&e[o]!==t[o])return!0;return!1}function i(e,t){if(!e||!t)return!0;if(e.length!==t.length)return!0;var o,l,i,s,r;for(o=0;o<t.length;o++){if(l=t[o],i=e[o],l.label!==i.label)return!0;if(l.color!==i.color)return!0;if(s=l.options.lines,r=i.options.lines,n(s,r))return!0;if(s=l.options.points,r=i.options.points,n(s,r))return!0;if(s=l.options.bars,r=i.options.bars,n(s,r))return!0}return!1}var s='<defs><symbol id="line" fill="none" viewBox="-5 -5 25 25"><polyline points="0,15 5,5 10,10 15,0"/></symbol><symbol id="area" stroke-width="1" viewBox="-5 -5 25 25"><polyline points="0,15 5,5 10,10 15,0, 15,15, 0,15"/></symbol><symbol id="bars" stroke-width="1" viewBox="-5 -5 25 25"><polyline points="1.5,15.5 1.5,12.5, 4.5,12.5 4.5,15.5 6.5,15.5 6.5,3.5, 9.5,3.5 9.5,15.5 11.5,15.5 11.5,7.5 14.5,7.5 14.5,15.5 1.5,15.5"/></symbol><symbol id="circle" viewBox="-5 -5 25 25"><circle cx="0" cy="15" r="2.5"/><circle cx="5" cy="5" r="2.5"/><circle cx="10" cy="10" r="2.5"/><circle cx="15" cy="0" r="2.5"/></symbol><symbol id="rectangle" viewBox="-5 -5 25 25"><rect x="-2.1" y="12.9" width="4.2" height="4.2"/><rect x="2.9" y="2.9" width="4.2" height="4.2"/><rect x="7.9" y="7.9" width="4.2" height="4.2"/><rect x="12.9" y="-2.1" width="4.2" height="4.2"/></symbol><symbol id="diamond" viewBox="-5 -5 25 25"><path d="M-3,15 L0,12 L3,15, L0,18 Z"/><path d="M2,5 L5,2 L8,5, L5,8 Z"/><path d="M7,10 L10,7 L13,10, L10,13 Z"/><path d="M12,0 L15,-3 L18,0, L15,3 Z"/></symbol><symbol id="cross" fill="none" viewBox="-5 -5 25 25"><path d="M-2.1,12.9 L2.1,17.1, M2.1,12.9 L-2.1,17.1 Z"/><path d="M2.9,2.9 L7.1,7.1 M7.1,2.9 L2.9,7.1 Z"/><path d="M7.9,7.9 L12.1,12.1 M12.1,7.9 L7.9,12.1 Z"/><path d="M12.9,-2.1 L17.1,2.1 M17.1,-2.1 L12.9,2.1 Z"/></symbol><symbol id="plus" fill="none" viewBox="-5 -5 25 25"><path d="M0,12 L0,18, M-3,15 L3,15 Z"/><path d="M5,2 L5,8 M2,5 L8,5 Z"/><path d="M10,7 L10,13 M7,10 L13,10 Z"/><path d="M15,-3 L15,3 M12,0 L18,0 Z"/></symbol></defs>';e.plot.plugins.push({init:function(e){e.hooks.setupGrid.push(function(e){var o=e.getOptions(),s=e.getData(),r=o.legend.labelFormatter,a=o.legend.legendEntries,h=o.legend.plotOffset,d=l(s,r,o.legend.sorted),c=e.getPlotOffset();(i(a,d)||n(h,c))&&t(e,o,e.getPlaceholder(),d)})},options:{legend:{show:!1,noColumns:1,labelFormatter:null,container:null,position:"ne",margin:5,sorted:null}},name:"legend",version:"1.0"})}(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.logaxis.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,298 @@
+/* Pretty handling of log axes.
+
+Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Copyright (c) 2015 Ciprian Ceteras cipix2000@gmail.com.
+Copyright (c) 2017 Raluca Portase
+Licensed under the MIT license.
+
+Set axis.mode to "log" to enable.
+*/
+
+/* global jQuery*/
+
+/**
+## jquery.flot.logaxis
+This plugin is used to create logarithmic axis. This includes tick generation,
+formatters and transformers to and from logarithmic representation.
+
+### Methods and hooks
+*/
+
+(function ($) {
+    'use strict';
+
+    var options = {
+        xaxis: {}
+    };
+
+    /*tick generators and formatters*/
+    var PREFERRED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 10),
+        EXTENDED_LOG_TICK_VALUES = computePreferedLogTickValues(Number.MAX_VALUE, 4);
+
+    function computePreferedLogTickValues(endLimit, rangeStep) {
+        var log10End = Math.floor(Math.log(endLimit) * Math.LOG10E) - 1,
+            log10Start = -log10End,
+            val, range, vals = [];
+
+        for (var power = log10Start; power <= log10End; power++) {
+            range = parseFloat('1e' + power);
+            for (var mult = 1; mult < 9; mult += rangeStep) {
+                val = range * mult;
+                vals.push(val);
+            }
+        }
+        return vals;
+    }
+
+    /**
+    - logTickGenerator(plot, axis, noTicks)
+
+    Generates logarithmic ticks, depending on axis range.
+    In case the number of ticks that can be generated is less than the expected noTicks/4,
+    a linear tick generation is used.
+    */
+    var logTickGenerator = function (plot, axis, noTicks) {
+        var ticks = [],
+            minIdx = -1,
+            maxIdx = -1,
+            surface = plot.getCanvas(),
+            logTickValues = PREFERRED_LOG_TICK_VALUES,
+            min = clampAxis(axis, plot),
+            max = axis.max;
+
+        if (!noTicks) {
+            noTicks = 0.3 * Math.sqrt(axis.direction === "x" ? surface.width : surface.height);
+        }
+
+        PREFERRED_LOG_TICK_VALUES.some(function (val, i) {
+            if (val >= min) {
+                minIdx = i;
+                return true;
+            } else {
+                return false;
+            }
+        });
+
+        PREFERRED_LOG_TICK_VALUES.some(function (val, i) {
+            if (val >= max) {
+                maxIdx = i;
+                return true;
+            } else {
+                return false;
+            }
+        });
+
+        if (maxIdx === -1) {
+            maxIdx = PREFERRED_LOG_TICK_VALUES.length - 1;
+        }
+
+        if (maxIdx - minIdx <= noTicks / 4 && logTickValues.length !== EXTENDED_LOG_TICK_VALUES.length) {
+            //try with multiple of 5 for tick values
+            logTickValues = EXTENDED_LOG_TICK_VALUES;
+            minIdx *= 2;
+            maxIdx *= 2;
+        }
+
+        var lastDisplayed = null,
+            inverseNoTicks = 1 / noTicks,
+            tickValue, pixelCoord, tick;
+
+        // Count the number of tick values would appear, if we can get at least
+        // nTicks / 4 accept them.
+        if (maxIdx - minIdx >= noTicks / 4) {
+            for (var idx = maxIdx; idx >= minIdx; idx--) {
+                tickValue = logTickValues[idx];
+                pixelCoord = (Math.log(tickValue) - Math.log(min)) / (Math.log(max) - Math.log(min));
+                tick = tickValue;
+
+                if (lastDisplayed === null) {
+                    lastDisplayed = {
+                        pixelCoord: pixelCoord,
+                        idealPixelCoord: pixelCoord
+                    };
+                } else {
+                    if (Math.abs(pixelCoord - lastDisplayed.pixelCoord) >= inverseNoTicks) {
+                        lastDisplayed = {
+                            pixelCoord: pixelCoord,
+                            idealPixelCoord: lastDisplayed.idealPixelCoord - inverseNoTicks
+                        };
+                    } else {
+                        tick = null;
+                    }
+                }
+
+                if (tick) {
+                    ticks.push(tick);
+                }
+            }
+            // Since we went in backwards order.
+            ticks.reverse();
+        } else {
+            var tickSize = plot.computeTickSize(min, max, noTicks),
+                customAxis = {min: min, max: max, tickSize: tickSize};
+            ticks = $.plot.linearTickGenerator(customAxis);
+        }
+
+        return ticks;
+    };
+
+    var clampAxis = function (axis, plot) {
+        var min = axis.min,
+            max = axis.max;
+
+        if (min <= 0) {
+            //for empty graph if axis.min is not strictly positive make it 0.1
+            if (axis.datamin === null) {
+                min = axis.min = 0.1;
+            } else {
+                min = processAxisOffset(plot, axis);
+            }
+
+            if (max < min) {
+                axis.max = axis.datamax !== null ? axis.datamax : axis.options.max;
+                axis.options.offset.below = 0;
+                axis.options.offset.above = 0;
+            }
+        }
+
+        return min;
+    }
+
+    /**
+    - logTickFormatter(value, axis, precision)
+
+    This is the corresponding tickFormatter of the logaxis.
+    For a number greater that 10^6 or smaller than 10^(-3), this will be drawn
+    with e representation
+    */
+    var logTickFormatter = function (value, axis, precision) {
+        var tenExponent = value > 0 ? Math.floor(Math.log(value) / Math.LN10) : 0;
+
+        if (precision) {
+            if ((tenExponent >= -4) && (tenExponent <= 7)) {
+                return $.plot.defaultTickFormatter(value, axis, precision);
+            } else {
+                return $.plot.expRepTickFormatter(value, axis, precision);
+            }
+        }
+        if ((tenExponent >= -4) && (tenExponent <= 7)) {
+            //if we have float numbers, return a limited length string(ex: 0.0009 is represented as 0.000900001)
+            var formattedValue = tenExponent < 0 ? value.toFixed(-tenExponent) : value.toFixed(tenExponent + 2);
+            if (formattedValue.indexOf('.') !== -1) {
+                var lastZero = formattedValue.lastIndexOf('0');
+
+                while (lastZero === formattedValue.length - 1) {
+                    formattedValue = formattedValue.slice(0, -1);
+                    lastZero = formattedValue.lastIndexOf('0');
+                }
+
+                //delete the dot if is last
+                if (formattedValue.indexOf('.') === formattedValue.length - 1) {
+                    formattedValue = formattedValue.slice(0, -1);
+                }
+            }
+            return formattedValue;
+        } else {
+            return $.plot.expRepTickFormatter(value, axis);
+        }
+    };
+
+    /*logaxis caracteristic functions*/
+    var logTransform = function (v) {
+        if (v < PREFERRED_LOG_TICK_VALUES[0]) {
+            v = PREFERRED_LOG_TICK_VALUES[0];
+        }
+
+        return Math.log(v);
+    };
+
+    var logInverseTransform = function (v) {
+        return Math.exp(v);
+    };
+
+    var invertedTransform = function (v) {
+        return -v;
+    }
+
+    var invertedLogTransform = function (v) {
+        return -logTransform(v);
+    }
+
+    var invertedLogInverseTransform = function (v) {
+        return logInverseTransform(-v);
+    }
+
+    /**
+    - setDataminRange(plot, axis)
+
+    It is used for clamping the starting point of a logarithmic axis.
+    This will set the axis datamin range to 0.1 or to the first datapoint greater then 0.
+    The function is usefull since the logarithmic representation can not show
+    values less than or equal to 0.
+    */
+    function setDataminRange(plot, axis) {
+        if (axis.options.mode === 'log' && axis.datamin <= 0) {
+            if (axis.datamin === null) {
+                axis.datamin = 0.1;
+            } else {
+                axis.datamin = processAxisOffset(plot, axis);
+            }
+        }
+    }
+
+    function processAxisOffset(plot, axis) {
+        var series = plot.getData(),
+            range = series
+                .filter(function(series) {
+                    return series.xaxis === axis || series.yaxis === axis;
+                })
+                .map(function(series) {
+                    return plot.computeRangeForDataSeries(series, null, isValid);
+                }),
+            min = axis.direction === 'x' 
+            ? Math.min(0.1, range && range[0] ? range[0].xmin : 0.1) 
+            : Math.min(0.1, range && range[0] ? range[0].ymin : 0.1);
+
+        axis.min = min;
+
+        return min;
+    }
+
+    function isValid(a) {
+        return a > 0;
+    }
+
+    function init(plot) {
+        plot.hooks.processOptions.push(function (plot) {
+            $.each(plot.getAxes(), function (axisName, axis) {
+                var opts = axis.options;
+                if (opts.mode === 'log') {
+                    axis.tickGenerator = function (axis) {
+                        var noTicks = 11;
+                        return logTickGenerator(plot, axis, noTicks);
+                    };
+                    if (typeof axis.options.tickFormatter !== 'function') {
+                        axis.options.tickFormatter = logTickFormatter;
+                    }
+                    axis.options.transform = opts.inverted ? invertedLogTransform : logTransform;
+                    axis.options.inverseTransform = opts.inverted ? invertedLogInverseTransform : logInverseTransform;
+                    axis.options.autoScaleMargin = 0;
+                    plot.hooks.setRange.push(setDataminRange);
+                } else if (opts.inverted) {
+                    axis.options.transform = invertedTransform;
+                    axis.options.inverseTransform = invertedTransform;
+                }
+            });
+        });
+    }
+
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: 'log',
+        version: '0.1'
+    });
+
+    $.plot.logTicksGenerator = logTickGenerator;
+    $.plot.logTickFormatter = logTickFormatter;
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.logaxis.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(t){"use strict";function n(t,n){for(var o,e,r=Math.floor(Math.log(t)*Math.LOG10E)-1,i=[],a=-r;a<=r;a++){e=parseFloat("1e"+a);for(var l=1;l<9;l+=n)o=e*l,i.push(o)}return i}function o(t,n){"log"===n.options.mode&&n.datamin<=0&&(null===n.datamin?n.datamin=.1:n.datamin=e(t,n))}function e(t,n){var o=t.getData().filter(function(t){return t.xaxis===n||t.yaxis===n}).map(function(n){return t.computeRangeForDataSeries(n,null,r)}),e="x"===n.direction?Math.min(.1,o&&o[0]?o[0].xmin:.1):Math.min(.1,o&&o[0]?o[0].ymin:.1);return n.min=e,e}function r(t){return t>0}var i=n(Number.MAX_VALUE,10),a=n(Number.MAX_VALUE,4),l=function(n,o,e){var r=[],l=-1,s=-1,f=n.getCanvas(),c=i,p=u(o,n),m=o.max;e||(e=.3*Math.sqrt("x"===o.direction?f.width:f.height)),i.some(function(t,n){return t>=p&&(l=n,!0)}),i.some(function(t,n){return t>=m&&(s=n,!0)}),-1===s&&(s=i.length-1),s-l<=e/4&&c.length!==a.length&&(c=a,l*=2,s*=2);var h,d,x,g=null,v=1/e;if(s-l>=e/4){for(var M=s;M>=l;M--)h=c[M],d=(Math.log(h)-Math.log(p))/(Math.log(m)-Math.log(p)),x=h,null===g?g={pixelCoord:d,idealPixelCoord:d}:Math.abs(d-g.pixelCoord)>=v?g={pixelCoord:d,idealPixelCoord:g.idealPixelCoord-v}:x=null,x&&r.push(x);r.reverse()}else{var k=n.computeTickSize(p,m,e),F={min:p,max:m,tickSize:k};r=t.plot.linearTickGenerator(F)}return r},u=function(t,n){var o=t.min,r=t.max;return o<=0&&r<(o=null===t.datamin?t.min=.1:e(n,t))&&(t.max=null!==t.datamax?t.datamax:t.options.max,t.options.offset.below=0,t.options.offset.above=0),o},s=function(n,o,e){var r=n>0?Math.floor(Math.log(n)/Math.LN10):0;if(e)return r>=-4&&r<=7?t.plot.defaultTickFormatter(n,o,e):t.plot.expRepTickFormatter(n,o,e);if(r>=-4&&r<=7){var i=r<0?n.toFixed(-r):n.toFixed(r+2);if(-1!==i.indexOf(".")){for(var a=i.lastIndexOf("0");a===i.length-1;)a=(i=i.slice(0,-1)).lastIndexOf("0");i.indexOf(".")===i.length-1&&(i=i.slice(0,-1))}return i}return t.plot.expRepTickFormatter(n,o)},f=function(t){return t<i[0]&&(t=i[0]),Math.log(t)},c=function(t){return Math.exp(t)},p=function(t){return-t},m=function(t){return-f(t)},h=function(t){return c(-t)};t.plot.plugins.push({init:function(n){n.hooks.processOptions.push(function(n){t.each(n.getAxes(),function(t,e){var r=e.options;"log"===r.mode?(e.tickGenerator=function(t){return l(n,t,11)},"function"!=typeof e.options.tickFormatter&&(e.options.tickFormatter=s),e.options.transform=r.inverted?m:f,e.options.inverseTransform=r.inverted?h:c,e.options.autoScaleMargin=0,n.hooks.setRange.push(o)):r.inverted&&(e.options.transform=p,e.options.inverseTransform=p)})})},options:{xaxis:{}},name:"log",version:"0.1"}),t.plot.logTicksGenerator=l,t.plot.logTickFormatter=s}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,8 +1,1 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
-
-Copyright (c) 2007-2014 IOLA and Ole Laursen.
-Licensed under the MIT license.
-
-*/
-(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;if(!$.fn.detach){$.fn.detach=function(){return this.each(function(){if(this.parentNode){this.parentNode.removeChild(this)}})}}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.x2axis.min==null){options.xaxes[1].min=null}if(options.x2axis.max==null){options.xaxes[1].max=null}}if(options.y2axis){options.yaxes[1]=$.extend(true,{},options.yaxis,options.y2axis);options.yaxes[1].position="right";if(options.y2axis.min==null){options.yaxes[1].min=null}if(options.y2axis.max==null){options.yaxes[1].max=null}}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.show||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){if(axis.direction==="x"){margins.left=Math.max(margins.left,axis.labelWidth/2);margins.right=Math.max(margins.right,axis.labelWidth/2)}else{margins.bottom=Math.max(margins.bottom,axis.labelHeight/2);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){var axisOpts=axis.options;axis.show=axisOpts.show==null?axis.used:axisOpts.show;axis.reserveSpace=axisOpts.reserveSpace==null?axis.show:axisOpts.reserveSpace;setRange(axis)});if(showGrid){var allocatedAxes=$.grep(axes,function(axis){return axis.show||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);var xequal=xrange.from===xrange.to,yequal=yrange.from===yrange.to;if(xequal&&yequal){continue}xrange.from=Math.floor(xrange.axis.p2c(xrange.from));xrange.to=Math.floor(xrange.axis.p2c(xrange.to));yrange.from=Math.floor(yrange.axis.p2c(yrange.from));yrange.to=Math.floor(yrange.axis.p2c(yrange.to));if(xequal||yequal){var lineWidth=m.lineWidth||options.grid.markingsLineWidth,subPixel=lineWidth%2?.5:0;ctx.beginPath();ctx.strokeStyle=m.color||options.grid.markingsColor;ctx.lineWidth=lineWidth;if(xequal){ctx.moveTo(xrange.to+subPixel,yrange.from);ctx.lineTo(xrange.to+subPixel,yrange.to)}else{ctx.moveTo(xrange.from,yrange.to+subPixel);ctx.lineTo(xrange.to,yrange.to+subPixel)}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.3";$.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);
\ No newline at end of file
+Invalid assignment
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.navigate.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.navigate.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,221 +1,457 @@
 /* Flot plugin for adding the ability to pan and zoom the plot.
 
 Copyright (c) 2007-2014 IOLA and Ole Laursen.
+Copyright (c) 2016 Ciprian Ceteras.
+Copyright (c) 2017 Raluca Portase.
 Licensed under the MIT license.
 
-The default behaviour is double click and scrollwheel up/down to zoom in, drag
+*/
+
+/**
+## jquery.flot.navigate.js
+
+This flot plugin is used for adding the ability to pan and zoom the plot.
+A higher level overview is available at [interactions](interactions.md) documentation.
+
+The default behaviour is scrollwheel up/down to zoom in, drag
 to pan. The plugin defines plot.zoom({ center }), plot.zoomOut() and
 plot.pan( offset ) so you easily can add custom controls. It also fires
 "plotpan" and "plotzoom" events, useful for synchronizing plots.
 
 The plugin supports these options:
+```js
+    zoom: {
+        interactive: false,
+        active: false,
+        amount: 1.5         // 2 = 200% (zoom in), 0.5 = 50% (zoom out)
+    }
 
-	zoom: {
-		interactive: false
-		trigger: "dblclick" // or "click" for single click
-		amount: 1.5         // 2 = 200% (zoom in), 0.5 = 50% (zoom out)
-	}
+    pan: {
+        interactive: false,
+        active: false,
+        cursor: "move",     // CSS mouse cursor value used when dragging, e.g. "pointer"
+        frameRate: 60,
+        mode: "smart"       // enable smart pan mode
+    }
 
-	pan: {
-		interactive: false
-		cursor: "move"      // CSS mouse cursor value used when dragging, e.g. "pointer"
-		frameRate: 20
-	}
+    xaxis: {
+        axisZoom: true, //zoom axis when mouse over it is allowed
+        plotZoom: true, //zoom axis is allowed for plot zoom
+        axisPan: true, //pan axis when mouse over it is allowed
+        plotPan: true //pan axis is allowed for plot pan
+    }
 
-	xaxis, yaxis, x2axis, y2axis: {
-		zoomRange: null  // or [ number, number ] (min range, max range) or false
-		panRange: null   // or [ number, number ] (min, max) or false
-	}
-
-"interactive" enables the built-in drag/click behaviour. If you enable
+    yaxis: {
+        axisZoom: true, //zoom axis when mouse over it is allowed
+        plotZoom: true, //zoom axis is allowed for plot zoom
+        axisPan: true, //pan axis when mouse over it is allowed
+        plotPan: true //pan axis is allowed for plot pan
+    }
+```
+**interactive** enables the built-in drag/click behaviour. If you enable
 interactive for pan, then you'll have a basic plot that supports moving
 around; the same for zoom.
 
-"amount" specifies the default amount to zoom in (so 1.5 = 150%) relative to
+**active** is true after a touch tap on plot. This enables plot navigation.
+Once activated, zoom and pan cannot be deactivated. When the plot becomes active,
+"plotactivated" event is triggered.
+
+**amount** specifies the default amount to zoom in (so 1.5 = 150%) relative to
 the current viewport.
 
-"cursor" is a standard CSS mouse cursor string used for visual feedback to the
+**cursor** is a standard CSS mouse cursor string used for visual feedback to the
 user when dragging.
 
-"frameRate" specifies the maximum number of times per second the plot will
+**frameRate** specifies the maximum number of times per second the plot will
 update itself while the user is panning around on it (set to null to disable
 intermediate pans, the plot will then not update until the mouse button is
 released).
 
-"zoomRange" is the interval in which zooming can happen, e.g. with zoomRange:
-[1, 100] the zoom will never scale the axis so that the difference between min
-and max is smaller than 1 or larger than 100. You can set either end to null
-to ignore, e.g. [1, null]. If you set zoomRange to false, zooming on that axis
-will be disabled.
-
-"panRange" confines the panning to stay within a range, e.g. with panRange:
-[-10, 20] panning stops at -10 in one end and at 20 in the other. Either can
-be null, e.g. [-10, null]. If you set panRange to false, panning on that axis
-will be disabled.
+**mode** a string specifies the pan mode for mouse interaction. Accepted values:
+'manual': no pan hint or direction snapping;
+'smart': The graph shows pan hint bar and the pan movement will snap
+to one direction when the drag direction is close to it;
+'smartLock'. The graph shows pan hint bar and the pan movement will always
+snap to a direction that the drag diorection started with.
 
 Example API usage:
-
-	plot = $.plot(...);
+```js
+    plot = $.plot(...);
 
-	// zoom default amount in on the pixel ( 10, 20 )
-	plot.zoom({ center: { left: 10, top: 20 } });
+    // zoom default amount in on the pixel ( 10, 20 )
+    plot.zoom({ center: { left: 10, top: 20 } });
 
-	// zoom out again
-	plot.zoomOut({ center: { left: 10, top: 20 } });
+    // zoom out again
+    plot.zoomOut({ center: { left: 10, top: 20 } });
 
-	// zoom 200% in on the pixel (10, 20)
-	plot.zoom({ amount: 2, center: { left: 10, top: 20 } });
+    // zoom 200% in on the pixel (10, 20)
+    plot.zoom({ amount: 2, center: { left: 10, top: 20 } });
 
-	// pan 100 pixels to the left and 20 down
-	plot.pan({ left: -100, top: 20 })
+    // pan 100 pixels to the left (changing x-range in a positive way) and 20 down
+    plot.pan({ left: -100, top: 20 })
+```
 
 Here, "center" specifies where the center of the zooming should happen. Note
 that this is defined in pixel space, not the space of the data points (you can
 use the p2c helpers on the axes in Flot to help you convert between these).
 
-"amount" is the amount to zoom the viewport relative to the current range, so
+**amount** is the amount to zoom the viewport relative to the current range, so
 1 is 100% (i.e. no change), 1.5 is 150% (zoom in), 0.7 is 70% (zoom out). You
 can set the default in the options.
-
 */
 
-// First two dependencies, jquery.event.drag.js and
-// jquery.mousewheel.js, we put them inline here to save people the
-// effort of downloading them.
-
-/*
-jquery.event.drag.js ~ v1.5 ~ Copyright (c) 2008, Three Dub Media (http://threedubmedia.com)
-Licensed under the MIT License ~ http://threedubmedia.googlecode.com/files/MIT-LICENSE.txt
-*/
-(function(a){function e(h){var k,j=this,l=h.data||{};if(l.elem)j=h.dragTarget=l.elem,h.dragProxy=d.proxy||j,h.cursorOffsetX=l.pageX-l.left,h.cursorOffsetY=l.pageY-l.top,h.offsetX=h.pageX-h.cursorOffsetX,h.offsetY=h.pageY-h.cursorOffsetY;else if(d.dragging||l.which>0&&h.which!=l.which||a(h.target).is(l.not))return;switch(h.type){case"mousedown":return a.extend(l,a(j).offset(),{elem:j,target:h.target,pageX:h.pageX,pageY:h.pageY}),b.add(document,"mousemove mouseup",e,l),i(j,!1),d.dragging=null,!1;case!d.dragging&&"mousemove":if(g(h.pageX-l.pageX)+g(h.pageY-l.pageY)<l.distance)break;h.target=l.target,k=f(h,"dragstart",j),k!==!1&&(d.dragging=j,d.proxy=h.dragProxy=a(k||j)[0]);case"mousemove":if(d.dragging){if(k=f(h,"drag",j),c.drop&&(c.drop.allowed=k!==!1,c.drop.handler(h)),k!==!1)break;h.type="mouseup"}case"mouseup":b.remove(document,"mousemove mouseup",e),d.dragging&&(c.drop&&c.drop.handler(h),f(h,"dragend",j)),i(j,!0),d.dragging=d.proxy=l.elem=!1}return!0}function f(b,c,d){b.type=c;var e=a.event.dispatch.call(d,b);return e===!1?!1:e||b.result}function g(a){return Math.pow(a,2)}function h(){return d.dragging===!1}function i(a,b){a&&(a.unselectable=b?"off":"on",a.onselectstart=function(){return b},a.style&&(a.style.MozUserSelect=b?"":"none"))}a.fn.drag=function(a,b,c){return b&&this.bind("dragstart",a),c&&this.bind("dragend",c),a?this.bind("drag",b?b:a):this.trigger("drag")};var b=a.event,c=b.special,d=c.drag={not:":input",distance:0,which:1,dragging:!1,setup:function(c){c=a.extend({distance:d.distance,which:d.which,not:d.not},c||{}),c.distance=g(c.distance),b.add(this,"mousedown",e,c),this.attachEvent&&this.attachEvent("ondragstart",h)},teardown:function(){b.remove(this,"mousedown",e),this===d.dragging&&(d.dragging=d.proxy=!1),i(this,!0),this.detachEvent&&this.detachEvent("ondragstart",h)}};c.dragstart=c.dragend={setup:function(){},teardown:function(){}}})(jQuery);
+/* eslint-enable */
+(function($) {
+    'use strict';
 
-/* jquery.mousewheel.min.js
- * Copyright (c) 2011 Brandon Aaron (http://brandonaaron.net)
- * Licensed under the MIT License (LICENSE.txt).
- * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
- * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
- * Thanks to: Seamus Leahy for adding deltaX and deltaY
- *
- * Version: 3.0.6
- *
- * Requires: 1.2.2+
- */
-(function(d){function e(a){var b=a||window.event,c=[].slice.call(arguments,1),f=0,e=0,g=0,a=d.event.fix(b);a.type="mousewheel";b.wheelDelta&&(f=b.wheelDelta/120);b.detail&&(f=-b.detail/3);g=f;void 0!==b.axis&&b.axis===b.HORIZONTAL_AXIS&&(g=0,e=-1*f);void 0!==b.wheelDeltaY&&(g=b.wheelDeltaY/120);void 0!==b.wheelDeltaX&&(e=-1*b.wheelDeltaX/120);c.unshift(a,f,e,g);return(d.event.dispatch||d.event.handle).apply(this,c)}var c=["DOMMouseScroll","mousewheel"];if(d.event.fixHooks)for(var h=c.length;h;)d.event.fixHooks[c[--h]]=d.event.mouseHooks;d.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var a=c.length;a;)this.addEventListener(c[--a],e,!1);else this.onmousewheel=e},teardown:function(){if(this.removeEventListener)for(var a=c.length;a;)this.removeEventListener(c[--a],e,!1);else this.onmousewheel=null}};d.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})})(jQuery);
-
-
-
-
-(function ($) {
     var options = {
-        xaxis: {
-            zoomRange: null, // or [number, number] (min range, max range)
-            panRange: null // or [number, number] (min, max)
-        },
         zoom: {
             interactive: false,
-            trigger: "dblclick", // or "click" for single click
+            active: false,
             amount: 1.5 // how much to zoom relative to current position, 2 = 200% (zoom in), 0.5 = 50% (zoom out)
         },
         pan: {
             interactive: false,
+            active: false,
             cursor: "move",
-            frameRate: 20
+            frameRate: 60,
+            mode: 'smart'
+        },
+        recenter: {
+            interactive: true
+        },
+        xaxis: {
+            axisZoom: true, //zoom axis when mouse over it is allowed
+            plotZoom: true, //zoom axis is allowed for plot zoom
+            axisPan: true, //pan axis when mouse over it is allowed
+            plotPan: true //pan axis is allowed for plot pan
+        },
+        yaxis: {
+            axisZoom: true,
+            plotZoom: true,
+            axisPan: true,
+            plotPan: true
         }
     };
 
+    var saturated = $.plot.saturated;
+    var browser = $.plot.browser;
+    var SNAPPING_CONSTANT = $.plot.uiConstants.SNAPPING_CONSTANT;
+    var PANHINT_LENGTH_CONSTANT = $.plot.uiConstants.PANHINT_LENGTH_CONSTANT;
+
     function init(plot) {
-        function onZoomClick(e, zoomOut) {
+        plot.hooks.processOptions.push(initNevigation);
+    }
+
+    function initNevigation(plot, options) {
+        var panAxes = null;
+        var canDrag = false;
+        var useManualPan = options.pan.mode === 'manual',
+            smartPanLock = options.pan.mode === 'smartLock',
+            useSmartPan = smartPanLock || options.pan.mode === 'smart';
+
+        function onZoomClick(e, zoomOut, amount) {
+            var page = browser.getPageXY(e);
+
             var c = plot.offset();
-            c.left = e.pageX - c.left;
-            c.top = e.pageY - c.top;
-            if (zoomOut)
-                plot.zoomOut({ center: c });
-            else
-                plot.zoom({ center: c });
+            c.left = page.X - c.left;
+            c.top = page.Y - c.top;
+
+            var ec = plot.getPlaceholder().offset();
+            ec.left = page.X - ec.left;
+            ec.top = page.Y - ec.top;
+
+            var axes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {
+                var box = axis.box;
+                if (box !== undefined) {
+                    return (ec.left > box.left) && (ec.left < box.left + box.width) &&
+                        (ec.top > box.top) && (ec.top < box.top + box.height);
+                }
+            });
+
+            if (axes.length === 0) {
+                axes = undefined;
+            }
+
+            if (zoomOut) {
+                plot.zoomOut({
+                    center: c,
+                    axes: axes,
+                    amount: amount
+                });
+            } else {
+                plot.zoom({
+                    center: c,
+                    axes: axes,
+                    amount: amount
+                });
+            }
         }
 
+        var prevCursor = 'default',
+            panHint = null,
+            panTimeout = null,
+            plotState,
+            prevDragPosition = { x: 0, y: 0 },
+            isPanAction = false;
+
         function onMouseWheel(e, delta) {
-            e.preventDefault();
-            onZoomClick(e, delta < 0);
-            return false;
+            var maxAbsoluteDeltaOnMac = 1,
+                isMacScroll = Math.abs(e.originalEvent.deltaY) <= maxAbsoluteDeltaOnMac,
+                defaultNonMacScrollAmount = null,
+                macMagicRatio = 50,
+                amount = isMacScroll ? 1 + Math.abs(e.originalEvent.deltaY) / macMagicRatio : defaultNonMacScrollAmount;
+
+            if (isPanAction) {
+                onDragEnd(e);
+            }
+
+            if (plot.getOptions().zoom.active) {
+                e.preventDefault();
+                onZoomClick(e, delta < 0, amount);
+                return false;
+            }
         }
-        
-        var prevCursor = 'default', prevPageX = 0, prevPageY = 0,
-            panTimeout = null;
+
+        plot.navigationState = function(startPageX, startPageY) {
+            var axes = this.getAxes();
+            var result = {};
+            Object.keys(axes).forEach(function(axisName) {
+                var axis = axes[axisName];
+                result[axisName] = {
+                    navigationOffset: { below: axis.options.offset.below || 0,
+                        above: axis.options.offset.above || 0},
+                    axisMin: axis.min,
+                    axisMax: axis.max,
+                    diagMode: false
+                }
+            });
+
+            result.startPageX = startPageX || 0;
+            result.startPageY = startPageY || 0;
+            return result;
+        }
+
+        function onMouseDown(e) {
+            canDrag = true;
+        }
+
+        function onMouseUp(e) {
+            canDrag = false;
+        }
+
+        function isLeftMouseButtonPressed(e) {
+            return e.button === 0;
+        }
 
         function onDragStart(e) {
-            if (e.which != 1)  // only accept left-click
+            if (!canDrag || !isLeftMouseButtonPressed(e)) {
                 return false;
+            }
+
+            isPanAction = true;
+            var page = browser.getPageXY(e);
+
+            var ec = plot.getPlaceholder().offset();
+            ec.left = page.X - ec.left;
+            ec.top = page.Y - ec.top;
+
+            panAxes = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {
+                var box = axis.box;
+                if (box !== undefined) {
+                    return (ec.left > box.left) && (ec.left < box.left + box.width) &&
+                        (ec.top > box.top) && (ec.top < box.top + box.height);
+                }
+            });
+
+            if (panAxes.length === 0) {
+                panAxes = undefined;
+            }
+
             var c = plot.getPlaceholder().css('cursor');
-            if (c)
+            if (c) {
                 prevCursor = c;
+            }
+
             plot.getPlaceholder().css('cursor', plot.getOptions().pan.cursor);
-            prevPageX = e.pageX;
-            prevPageY = e.pageY;
+
+            if (useSmartPan) {
+                plotState = plot.navigationState(page.X, page.Y);
+            } else if (useManualPan) {
+                prevDragPosition.x = page.X;
+                prevDragPosition.y = page.Y;
+            }
         }
         
         function onDrag(e) {
+            if (!isPanAction) { 
+                return; 
+            }
+
+            var page = browser.getPageXY(e);
             var frameRate = plot.getOptions().pan.frameRate;
-            if (panTimeout || !frameRate)
-                return;
 
-            panTimeout = setTimeout(function () {
-                plot.pan({ left: prevPageX - e.pageX,
-                           top: prevPageY - e.pageY });
-                prevPageX = e.pageX;
-                prevPageY = e.pageY;
-                                                    
+            if (frameRate === -1) {
+                if (useSmartPan) {
+                    plot.smartPan({
+                        x: plotState.startPageX - page.X,
+                        y: plotState.startPageY - page.Y
+                    }, plotState, panAxes, false, smartPanLock);
+                } else if (useManualPan) {
+                    plot.pan({
+                        left: prevDragPosition.x - page.X,
+                        top: prevDragPosition.y - page.Y,
+                        axes: panAxes
+                    });
+                    prevDragPosition.x = page.X;
+                    prevDragPosition.y = page.Y;
+                }
+                return;
+            }
+
+            if (panTimeout || !frameRate) return;
+
+            panTimeout = setTimeout(function() {
+                if (useSmartPan) {
+                    plot.smartPan({
+                        x: plotState.startPageX - page.X,
+                        y: plotState.startPageY - page.Y
+                    }, plotState, panAxes, false, smartPanLock);
+                } else if (useManualPan) {
+                    plot.pan({
+                        left: prevDragPosition.x - page.X,
+                        top: prevDragPosition.y - page.Y,
+                        axes: panAxes
+                    });
+                    prevDragPosition.x = page.X;
+                    prevDragPosition.y = page.Y;
+                }
+
                 panTimeout = null;
             }, 1 / frameRate * 1000);
         }
 
         function onDragEnd(e) {
+            if (!isPanAction) { 
+                return; 
+            }
+
             if (panTimeout) {
                 clearTimeout(panTimeout);
                 panTimeout = null;
             }
-                    
+
+            isPanAction = false;
+            var page = browser.getPageXY(e);
+
             plot.getPlaceholder().css('cursor', prevCursor);
-            plot.pan({ left: prevPageX - e.pageX,
-                       top: prevPageY - e.pageY });
+
+            if (useSmartPan) {
+                plot.smartPan({
+                    x: plotState.startPageX - page.X,
+                    y: plotState.startPageY - page.Y
+                }, plotState, panAxes, false, smartPanLock);
+                plot.smartPan.end();
+            } else if (useManualPan) {
+                plot.pan({
+                    left: prevDragPosition.x - page.X,
+                    top: prevDragPosition.y - page.Y,
+                    axes: panAxes
+                });
+                prevDragPosition.x = 0;
+                prevDragPosition.y = 0;
+            }
         }
-        
+
+        function onDblClick(e) {
+            plot.activate();
+            var o = plot.getOptions()
+
+            if (!o.recenter.interactive) { return; }
+
+            var axes = plot.getTouchedAxis(e.clientX, e.clientY),
+                event;
+
+            plot.recenter({ axes: axes[0] ? axes : null });
+
+            if (axes[0]) {
+                event = new $.Event('re-center', { detail: {
+                    axisTouched: axes[0]
+                }});
+            } else {
+                event = new $.Event('re-center', { detail: e });
+            }
+            plot.getPlaceholder().trigger(event);
+        }
+
+        function onClick(e) {
+            plot.activate();
+
+            if (isPanAction) {
+                onDragEnd(e);
+            }
+
+            return false;
+        }
+
+        plot.activate = function() {
+            var o = plot.getOptions();
+            if (!o.pan.active || !o.zoom.active) {
+                o.pan.active = true;
+                o.zoom.active = true;
+                plot.getPlaceholder().trigger("plotactivated", [plot]);
+            }
+        }
+
         function bindEvents(plot, eventHolder) {
             var o = plot.getOptions();
             if (o.zoom.interactive) {
-                eventHolder[o.zoom.trigger](onZoomClick);
                 eventHolder.mousewheel(onMouseWheel);
             }
 
             if (o.pan.interactive) {
-                eventHolder.bind("dragstart", { distance: 10 }, onDragStart);
-                eventHolder.bind("drag", onDrag);
-                eventHolder.bind("dragend", onDragEnd);
+                plot.addEventHandler("dragstart", onDragStart, eventHolder, 0);
+                plot.addEventHandler("drag", onDrag, eventHolder, 0);
+                plot.addEventHandler("dragend", onDragEnd, eventHolder, 0);
+                eventHolder.bind("mousedown", onMouseDown);
+                eventHolder.bind("mouseup", onMouseUp);
             }
+
+            eventHolder.dblclick(onDblClick);
+            eventHolder.click(onClick);
         }
 
-        plot.zoomOut = function (args) {
-            if (!args)
+        plot.zoomOut = function(args) {
+            if (!args) {
                 args = {};
-            
-            if (!args.amount)
+            }
+
+            if (!args.amount) {
                 args.amount = plot.getOptions().zoom.amount;
+            }
 
             args.amount = 1 / args.amount;
             plot.zoom(args);
         };
-        
-        plot.zoom = function (args) {
-            if (!args)
+
+        plot.zoom = function(args) {
+            if (!args) {
                 args = {};
-            
+            }
+
             var c = args.center,
                 amount = args.amount || plot.getOptions().zoom.amount,
-                w = plot.width(), h = plot.height();
+                w = plot.width(),
+                h = plot.height(),
+                axes = args.axes || plot.getAxes();
 
-            if (!c)
-                c = { left: w / 2, top: h / 2 };
-                
+            if (!c) {
+                c = {
+                    left: w / 2,
+                    top: h / 2
+                };
+            }
+
             var xf = c.left / w,
                 yf = c.top / h,
                 minmax = {
@@ -229,18 +465,24 @@
                     }
                 };
 
-            $.each(plot.getAxes(), function(_, axis) {
-                var opts = axis.options,
+            for (var key in axes) {
+                if (!axes.hasOwnProperty(key)) {
+                    continue;
+                }
+
+                var axis = axes[key],
+                    opts = axis.options,
                     min = minmax[axis.direction].min,
                     max = minmax[axis.direction].max,
-                    zr = opts.zoomRange,
-                    pr = opts.panRange;
+                    navigationOffset = axis.options.offset;
 
-                if (zr === false) // no zooming on this axis
-                    return;
-                    
-                min = axis.c2p(min);
-                max = axis.c2p(max);
+                //skip axis without axisZoom when zooming only on certain axis or axis without plotZoom for zoom on entire plot
+                if ((!opts.axisZoom && args.axes) || (!args.axes && !opts.plotZoom)) {
+                    continue;
+                }
+
+                min = $.plot.saturated.saturate(axis.c2p(min));
+                max = $.plot.saturated.saturate(axis.c2p(max));
                 if (min > max) {
                     // make sure min < max
                     var tmp = min;
@@ -248,95 +490,305 @@
                     max = tmp;
                 }
 
-                //Check that we are in panRange
-                if (pr) {
-                    if (pr[0] != null && min < pr[0]) {
-                        min = pr[0];
-                    }
-                    if (pr[1] != null && max > pr[1]) {
-                        max = pr[1];
-                    }
-                }
+                var offsetBelow = $.plot.saturated.saturate(navigationOffset.below - (axis.min - min));
+                var offsetAbove = $.plot.saturated.saturate(navigationOffset.above - (axis.max - max));
+                opts.offset = { below: offsetBelow, above: offsetAbove };
+            };
 
-                var range = max - min;
-                if (zr &&
-                    ((zr[0] != null && range < zr[0] && amount >1) ||
-                     (zr[1] != null && range > zr[1] && amount <1)))
-                    return;
-            
-                opts.min = min;
-                opts.max = max;
-            });
-            
-            plot.setupGrid();
+            plot.setupGrid(true);
             plot.draw();
-            
-            if (!args.preventEvent)
-                plot.getPlaceholder().trigger("plotzoom", [ plot, args ]);
+
+            if (!args.preventEvent) {
+                plot.getPlaceholder().trigger("plotzoom", [plot, args]);
+            }
         };
 
-        plot.pan = function (args) {
+        plot.pan = function(args) {
             var delta = {
                 x: +args.left,
                 y: +args.top
             };
 
-            if (isNaN(delta.x))
-                delta.x = 0;
-            if (isNaN(delta.y))
-                delta.y = 0;
+            if (isNaN(delta.x)) delta.x = 0;
+            if (isNaN(delta.y)) delta.y = 0;
+
+            $.each(args.axes || plot.getAxes(), function(_, axis) {
+                var opts = axis.options,
+                    d = delta[axis.direction];
+
+                //skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot
+                if ((!opts.axisPan && args.axes) || (!opts.plotPan && !args.axes)) {
+                    return;
+                }
+
+                if (d !== 0) {
+                    var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axis.min) + d) - axis.c2p(axis.p2c(axis.min))),
+                        navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axis.max) + d) - axis.c2p(axis.p2c(axis.max)));
+
+                    if (!isFinite(navigationOffsetBelow)) {
+                        navigationOffsetBelow = 0;
+                    }
+
+                    if (!isFinite(navigationOffsetAbove)) {
+                        navigationOffsetAbove = 0;
+                    }
+
+                    opts.offset = {
+                        below: saturated.saturate(navigationOffsetBelow + (opts.offset.below || 0)),
+                        above: saturated.saturate(navigationOffsetAbove + (opts.offset.above || 0))
+                    };
+                }
+            });
 
-            $.each(plot.getAxes(), function (_, axis) {
-                var opts = axis.options,
-                    min, max, d = delta[axis.direction];
+            plot.setupGrid(true);
+            plot.draw();
+            if (!args.preventEvent) {
+                plot.getPlaceholder().trigger("plotpan", [plot, args]);
+            }
+        };
 
-                min = axis.c2p(axis.p2c(axis.min) + d),
-                max = axis.c2p(axis.p2c(axis.max) + d);
+        plot.recenter = function(args) {
+            $.each(args.axes || plot.getAxes(), function(_, axis) {
+                if (args.axes) {
+                    if (this.direction === 'x') {
+                        axis.options.offset = { below: 0 };
+                    } else if (this.direction === 'y') {
+                        axis.options.offset = { above: 0 };
+                    }
+                } else {
+                    axis.options.offset = { below: 0, above: 0 };
+                }
+            });
+            plot.setupGrid(true);
+            plot.draw();
+        };
+
+        var shouldSnap = function(delta) {
+            return (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) ||
+                (Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT);
+        }
+
+        // adjust delta so the pan action is constrained on the vertical or horizontal direction
+        // it the movements in the other direction are small
+        var adjustDeltaToSnap = function(delta) {
+            if (Math.abs(delta.x) < SNAPPING_CONSTANT && Math.abs(delta.y) >= SNAPPING_CONSTANT) {
+                return {x: 0, y: delta.y};
+            }
 
-                var pr = opts.panRange;
-                if (pr === false) // no panning on this axis
-                    return;
-                
-                if (pr) {
-                    // check whether we hit the wall
-                    if (pr[0] != null && pr[0] > min) {
-                        d = pr[0] - min;
-                        min += d;
-                        max += d;
-                    }
-                    
-                    if (pr[1] != null && pr[1] < max) {
-                        d = pr[1] - max;
-                        min += d;
-                        max += d;
+            if (Math.abs(delta.y) < SNAPPING_CONSTANT && Math.abs(delta.x) >= SNAPPING_CONSTANT) {
+                return {x: delta.x, y: 0};
+            }
+
+            return delta;
+        }
+
+        var lockedDirection = null;
+        var lockDeltaDirection = function(delta) {
+            if (!lockedDirection && Math.max(Math.abs(delta.x), Math.abs(delta.y)) >= SNAPPING_CONSTANT) {
+                lockedDirection = Math.abs(delta.x) < Math.abs(delta.y) ? 'y' : 'x';
+            }
+
+            switch (lockedDirection) {
+                case 'x':
+                    return { x: delta.x, y: 0 };
+                case 'y':
+                    return { x: 0, y: delta.y };
+                default:
+                    return { x: 0, y: 0 };
+            }
+        }
+
+        var isDiagonalMode = function(delta) {
+            if (Math.abs(delta.x) > 0 && Math.abs(delta.y) > 0) {
+                return true;
+            }
+            return false;
+        }
+
+        var restoreAxisOffset = function(axes, initialState, delta) {
+            var axis;
+            Object.keys(axes).forEach(function(axisName) {
+                axis = axes[axisName];
+                if (delta[axis.direction] === 0) {
+                    axis.options.offset.below = initialState[axisName].navigationOffset.below;
+                    axis.options.offset.above = initialState[axisName].navigationOffset.above;
+                }
+            });
+        }
+
+        var prevDelta = { x: 0, y: 0 };
+        plot.smartPan = function(delta, initialState, panAxes, preventEvent, smartLock) {
+            var snap = smartLock ? true : shouldSnap(delta),
+                axes = plot.getAxes(),
+                opts;
+            delta = smartLock ? lockDeltaDirection(delta) : adjustDeltaToSnap(delta);
+
+            if (isDiagonalMode(delta)) {
+                initialState.diagMode = true;
+            }
+
+            if (snap && initialState.diagMode === true) {
+                initialState.diagMode = false;
+                restoreAxisOffset(axes, initialState, delta);
+            }
+
+            if (snap) {
+                panHint = {
+                    start: {
+                        x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left,
+                        y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top
+                    },
+                    end: {
+                        x: initialState.startPageX - delta.x - plot.offset().left + plot.getPlotOffset().left,
+                        y: initialState.startPageY - delta.y - plot.offset().top + plot.getPlotOffset().top
                     }
                 }
-                
-                opts.min = min;
-                opts.max = max;
+            } else {
+                panHint = {
+                    start: {
+                        x: initialState.startPageX - plot.offset().left + plot.getPlotOffset().left,
+                        y: initialState.startPageY - plot.offset().top + plot.getPlotOffset().top
+                    },
+                    end: false
+                }
+            }
+
+            if (isNaN(delta.x)) delta.x = 0;
+            if (isNaN(delta.y)) delta.y = 0;
+
+            if (panAxes) {
+                axes = panAxes;
+            }
+
+            var axis, axisMin, axisMax, p, d;
+            Object.keys(axes).forEach(function(axisName) {
+                axis = axes[axisName];
+                axisMin = axis.min;
+                axisMax = axis.max;
+                opts = axis.options;
+
+                d = delta[axis.direction];
+                p = prevDelta[axis.direction];
+
+                //skip axis without axisPan when panning only on certain axis or axis without plotPan for pan the entire plot
+                if ((!opts.axisPan && panAxes) || (!panAxes && !opts.plotPan)) {
+                    return;
+                }
+
+                if (d !== 0) {
+                    var navigationOffsetBelow = saturated.saturate(axis.c2p(axis.p2c(axisMin) - (p - d)) - axis.c2p(axis.p2c(axisMin))),
+                        navigationOffsetAbove = saturated.saturate(axis.c2p(axis.p2c(axisMax) - (p - d)) - axis.c2p(axis.p2c(axisMax)));
+
+                    if (!isFinite(navigationOffsetBelow)) {
+                        navigationOffsetBelow = 0;
+                    }
+
+                    if (!isFinite(navigationOffsetAbove)) {
+                        navigationOffsetAbove = 0;
+                    }
+
+                    axis.options.offset.below = saturated.saturate(navigationOffsetBelow + (axis.options.offset.below || 0));
+                    axis.options.offset.above = saturated.saturate(navigationOffsetAbove + (axis.options.offset.above || 0));
+                }
             });
-            
-            plot.setupGrid();
+
+            prevDelta = delta;
+            plot.setupGrid(true);
             plot.draw();
-            
-            if (!args.preventEvent)
-                plot.getPlaceholder().trigger("plotpan", [ plot, args ]);
+
+            if (!preventEvent) {
+                plot.getPlaceholder().trigger("plotpan", [plot, delta, panAxes, initialState]);
+            }
         };
 
+        plot.smartPan.end = function() {
+            panHint = null;
+            lockedDirection = null;
+            prevDelta = { x: 0, y: 0 };
+            plot.triggerRedrawOverlay();
+        }
+
         function shutdown(plot, eventHolder) {
-            eventHolder.unbind(plot.getOptions().zoom.trigger, onZoomClick);
             eventHolder.unbind("mousewheel", onMouseWheel);
+            eventHolder.unbind("mousedown", onMouseDown);
+            eventHolder.unbind("mouseup", onMouseUp);
             eventHolder.unbind("dragstart", onDragStart);
             eventHolder.unbind("drag", onDrag);
             eventHolder.unbind("dragend", onDragEnd);
-            if (panTimeout)
-                clearTimeout(panTimeout);
+            eventHolder.unbind("dblclick", onDblClick);
+            eventHolder.unbind("click", onClick);
+
+            if (panTimeout) clearTimeout(panTimeout);
         }
-        
+
+        function drawOverlay(plot, ctx) {
+            if (panHint) {
+                ctx.strokeStyle = 'rgba(96, 160, 208, 0.7)';
+                ctx.lineWidth = 2;
+                ctx.lineJoin = "round";
+                var startx = Math.round(panHint.start.x),
+                    starty = Math.round(panHint.start.y),
+                    endx, endy;
+
+                if (panAxes) {
+                    if (panAxes[0].direction === 'x') {
+                        endy = Math.round(panHint.start.y);
+                        endx = Math.round(panHint.end.x);
+                    } else if (panAxes[0].direction === 'y') {
+                        endx = Math.round(panHint.start.x);
+                        endy = Math.round(panHint.end.y);
+                    }
+                } else {
+                    endx = Math.round(panHint.end.x);
+                    endy = Math.round(panHint.end.y);
+                }
+
+                ctx.beginPath();
+
+                if (panHint.end === false) {
+                    ctx.moveTo(startx, starty - PANHINT_LENGTH_CONSTANT);
+                    ctx.lineTo(startx, starty + PANHINT_LENGTH_CONSTANT);
+
+                    ctx.moveTo(startx + PANHINT_LENGTH_CONSTANT, starty);
+                    ctx.lineTo(startx - PANHINT_LENGTH_CONSTANT, starty);
+                } else {
+                    var dirX = starty === endy;
+
+                    ctx.moveTo(startx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty - (dirX ? PANHINT_LENGTH_CONSTANT : 0));
+                    ctx.lineTo(startx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), starty + (dirX ? PANHINT_LENGTH_CONSTANT : 0));
+
+                    ctx.moveTo(startx, starty);
+                    ctx.lineTo(endx, endy);
+
+                    ctx.moveTo(endx - (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy - (dirX ? PANHINT_LENGTH_CONSTANT : 0));
+                    ctx.lineTo(endx + (dirX ? 0 : PANHINT_LENGTH_CONSTANT), endy + (dirX ? PANHINT_LENGTH_CONSTANT : 0));
+                }
+
+                ctx.stroke();
+            }
+        }
+
+        plot.getTouchedAxis = function(touchPointX, touchPointY) {
+            var ec = plot.getPlaceholder().offset();
+            ec.left = touchPointX - ec.left;
+            ec.top = touchPointY - ec.top;
+
+            var axis = plot.getXAxes().concat(plot.getYAxes()).filter(function (axis) {
+                var box = axis.box;
+                if (box !== undefined) {
+                    return (ec.left > box.left) && (ec.left < box.left + box.width) &&
+                            (ec.top > box.top) && (ec.top < box.top + box.height);
+                }
+            });
+
+            return axis;
+        }
+
+        plot.hooks.drawOverlay.push(drawOverlay);
         plot.hooks.bindEvents.push(bindEvents);
         plot.hooks.shutdown.push(shutdown);
     }
-    
+
     $.plot.plugins.push({
         init: init,
         options: options,
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.navigate.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.navigate.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,1 +1,1 @@
-!function(e){function t(a){var g,d=this,l=a.data||{};if(l.elem)d=a.dragTarget=l.elem,a.dragProxy=u.proxy||d,a.cursorOffsetX=l.pageX-l.left,a.cursorOffsetY=l.pageY-l.top,a.offsetX=a.pageX-a.cursorOffsetX,a.offsetY=a.pageY-a.cursorOffsetY;else if(u.dragging||l.which>0&&a.which!=l.which||e(a.target).is(l.not))return;switch(a.type){case"mousedown":return e.extend(l,e(d).offset(),{elem:d,target:a.target,pageX:a.pageX,pageY:a.pageY}),i.add(document,"mousemove mouseup",t,l),r(d,!1),u.dragging=null,!1;case!u.dragging&&"mousemove":if(o(a.pageX-l.pageX)+o(a.pageY-l.pageY)<l.distance)break;a.target=l.target,!1!==(g=n(a,"dragstart",d))&&(u.dragging=d,u.proxy=a.dragProxy=e(g||d)[0]);case"mousemove":if(u.dragging){if(g=n(a,"drag",d),s.drop&&(s.drop.allowed=!1!==g,s.drop.handler(a)),!1!==g)break;a.type="mouseup"}case"mouseup":i.remove(document,"mousemove mouseup",t),u.dragging&&(s.drop&&s.drop.handler(a),n(a,"dragend",d)),r(d,!0),u.dragging=u.proxy=l.elem=!1}return!0}function n(t,n,o){t.type=n;var a=e.event.dispatch.call(o,t);return!1!==a&&(a||t.result)}function o(e){return Math.pow(e,2)}function a(){return!1===u.dragging}function r(e,t){e&&(e.unselectable=t?"off":"on",e.onselectstart=function(){return t},e.style&&(e.style.MozUserSelect=t?"":"none"))}e.fn.drag=function(e,t,n){return t&&this.bind("dragstart",e),n&&this.bind("dragend",n),e?this.bind("drag",t||e):this.trigger("drag")};var i=e.event,s=i.special,u=s.drag={not:":input",distance:0,which:1,dragging:!1,setup:function(n){(n=e.extend({distance:u.distance,which:u.which,not:u.not},n||{})).distance=o(n.distance),i.add(this,"mousedown",t,n),this.attachEvent&&this.attachEvent("ondragstart",a)},teardown:function(){i.remove(this,"mousedown",t),this===u.dragging&&(u.dragging=u.proxy=!1),r(this,!0),this.detachEvent&&this.detachEvent("ondragstart",a)}};s.dragstart=s.dragend={setup:function(){},teardown:function(){}}}(jQuery),function(e){function t(t){var n=t||window.event,o=[].slice.call(arguments,1),a=0,r=0,i=0;return(t=e.event.fix(n)).type="mousewheel",n.wheelDelta&&(a=n.wheelDelta/120),n.detail&&(a=-n.detail/3),i=a,void 0!==n.axis&&n.axis===n.HORIZONTAL_AXIS&&(i=0,r=-1*a),void 0!==n.wheelDeltaY&&(i=n.wheelDeltaY/120),void 0!==n.wheelDeltaX&&(r=-1*n.wheelDeltaX/120),o.unshift(t,a,r,i),(e.event.dispatch||e.event.handle).apply(this,o)}var n=["DOMMouseScroll","mousewheel"];if(e.event.fixHooks)for(var o=n.length;o;)e.event.fixHooks[n[--o]]=e.event.mouseHooks;e.event.special.mousewheel={setup:function(){if(this.addEventListener)for(var e=n.length;e;)this.addEventListener(n[--e],t,!1);else this.onmousewheel=t},teardown:function(){if(this.removeEventListener)for(var e=n.length;e;)this.removeEventListener(n[--e],t,!1);else this.onmousewheel=null}},e.fn.extend({mousewheel:function(e){return e?this.bind("mousewheel",e):this.trigger("mousewheel")},unmousewheel:function(e){return this.unbind("mousewheel",e)}})}(jQuery),function(e){e.plot.plugins.push({init:function(t){function n(e,n){var o=t.offset();o.left=e.pageX-o.left,o.top=e.pageY-o.top,n?t.zoomOut({center:o}):t.zoom({center:o})}function o(e,t){return e.preventDefault(),n(e,t<0),!1}function a(e){if(1!=e.which)return!1;var n=t.getPlaceholder().css("cursor");n&&(s=n),t.getPlaceholder().css("cursor",t.getOptions().pan.cursor),u=e.pageX,g=e.pageY}function r(e){var n=t.getOptions().pan.frameRate;!d&&n&&(d=setTimeout(function(){t.pan({left:u-e.pageX,top:g-e.pageY}),u=e.pageX,g=e.pageY,d=null},1/n*1e3))}function i(e){d&&(clearTimeout(d),d=null),t.getPlaceholder().css("cursor",s),t.pan({left:u-e.pageX,top:g-e.pageY})}var s="default",u=0,g=0,d=null;t.zoomOut=function(e){e||(e={}),e.amount||(e.amount=t.getOptions().zoom.amount),e.amount=1/e.amount,t.zoom(e)},t.zoom=function(n){n||(n={});var o=n.center,a=n.amount||t.getOptions().zoom.amount,r=t.width(),i=t.height();o||(o={left:r/2,top:i/2});var s=o.left/r,u=o.top/i,g={x:{min:o.left-s*r/a,max:o.left+(1-s)*r/a},y:{min:o.top-u*i/a,max:o.top+(1-u)*i/a}};e.each(t.getAxes(),function(e,t){var n=t.options,o=g[t.direction].min,r=g[t.direction].max,i=n.zoomRange,s=n.panRange;if(!1!==i){if(o=t.c2p(o),r=t.c2p(r),o>r){var u=o;o=r,r=u}s&&(null!=s[0]&&o<s[0]&&(o=s[0]),null!=s[1]&&r>s[1]&&(r=s[1]));var d=r-o;i&&(null!=i[0]&&d<i[0]&&a>1||null!=i[1]&&d>i[1]&&a<1)||(n.min=o,n.max=r)}}),t.setupGrid(),t.draw(),n.preventEvent||t.getPlaceholder().trigger("plotzoom",[t,n])},t.pan=function(n){var o={x:+n.left,y:+n.top};isNaN(o.x)&&(o.x=0),isNaN(o.y)&&(o.y=0),e.each(t.getAxes(),function(e,t){var n,a,r=t.options,i=o[t.direction];n=t.c2p(t.p2c(t.min)+i),a=t.c2p(t.p2c(t.max)+i);var s=r.panRange;!1!==s&&(s&&(null!=s[0]&&s[0]>n&&(n+=i=s[0]-n,a+=i),null!=s[1]&&s[1]<a&&(n+=i=s[1]-a,a+=i)),r.min=n,r.max=a)}),t.setupGrid(),t.draw(),n.preventEvent||t.getPlaceholder().trigger("plotpan",[t,n])},t.hooks.bindEvents.push(function(e,t){var s=e.getOptions();s.zoom.interactive&&(t[s.zoom.trigger](n),t.mousewheel(o)),s.pan.interactive&&(t.bind("dragstart",{distance:10},a),t.bind("drag",r),t.bind("dragend",i))}),t.hooks.shutdown.push(function(e,t){t.unbind(e.getOptions().zoom.trigger,n),t.unbind("mousewheel",o),t.unbind("dragstart",a),t.unbind("drag",r),t.unbind("dragend",i),d&&clearTimeout(d)})},options:{xaxis:{zoomRange:null,panRange:null},zoom:{interactive:!1,trigger:"dblclick",amount:1.5},pan:{interactive:!1,cursor:"move",frameRate:20}},name:"navigate",version:"1.3"})}(jQuery);
+!function(t){"use strict";function e(e,r){function s(t,a,n){var i=o.getPageXY(t),r=e.offset();r.left=i.X-r.left,r.top=i.Y-r.top;var s=e.getPlaceholder().offset();s.left=i.X-s.left,s.top=i.Y-s.top;var f=e.getXAxes().concat(e.getYAxes()).filter(function(t){var e=t.box;if(void 0!==e)return s.left>e.left&&s.left<e.left+e.width&&s.top>e.top&&s.top<e.top+e.height});0===f.length&&(f=void 0),a?e.zoomOut({center:r,axes:f,amount:n}):e.zoom({center:r,axes:f,amount:n})}function f(t,a){var o=Math.abs(t.originalEvent.deltaY)<=1?1+Math.abs(t.originalEvent.deltaY)/50:null;if(T&&g(t),e.getOptions().zoom.active)return t.preventDefault(),s(t,a<0,o),!1}function l(t){b=!0}function u(t){b=!1}function c(t){return 0===t.button}function p(t){if(!b||!c(t))return!1;T=!0;var a=o.getPageXY(t),n=e.getPlaceholder().offset();n.left=a.X-n.left,n.top=a.Y-n.top,0===(h=e.getXAxes().concat(e.getYAxes()).filter(function(t){var e=t.box;if(void 0!==e)return n.left>e.left&&n.left<e.left+e.width&&n.top>e.top&&n.top<e.top+e.height})).length&&(h=void 0);var i=e.getPlaceholder().css("cursor");i&&(M=i),e.getPlaceholder().css("cursor",e.getOptions().pan.cursor),w?m=e.navigationState(a.X,a.Y):y&&(X.x=a.X,X.y=a.Y)}function d(t){if(T){var a=o.getPageXY(t),n=e.getOptions().pan.frameRate;-1!==n?!O&&n&&(O=setTimeout(function(){w?e.smartPan({x:m.startPageX-a.X,y:m.startPageY-a.Y},m,h,!1,P):y&&(e.pan({left:X.x-a.X,top:X.y-a.Y,axes:h}),X.x=a.X,X.y=a.Y),O=null},1/n*1e3)):w?e.smartPan({x:m.startPageX-a.X,y:m.startPageY-a.Y},m,h,!1,P):y&&(e.pan({left:X.x-a.X,top:X.y-a.Y,axes:h}),X.x=a.X,X.y=a.Y)}}function g(t){if(T){O&&(clearTimeout(O),O=null),T=!1;var a=o.getPageXY(t);e.getPlaceholder().css("cursor",M),w?(e.smartPan({x:m.startPageX-a.X,y:m.startPageY-a.Y},m,h,!1,P),e.smartPan.end()):y&&(e.pan({left:X.x-a.X,top:X.y-a.Y,axes:h}),X.x=0,X.y=0)}}function x(a){if(e.activate(),e.getOptions().recenter.interactive){var o,n=e.getTouchedAxis(a.clientX,a.clientY);e.recenter({axes:n[0]?n:null}),o=n[0]?new t.Event("re-center",{detail:{axisTouched:n[0]}}):new t.Event("re-center",{detail:a}),e.getPlaceholder().trigger(o)}}function v(t){return e.activate(),T&&g(t),!1}var m,h=null,b=!1,y="manual"===r.pan.mode,P="smartLock"===r.pan.mode,w=P||"smart"===r.pan.mode,M="default",Y=null,O=null,X={x:0,y:0},T=!1;e.navigationState=function(t,e){var a=this.getAxes(),o={};return Object.keys(a).forEach(function(t){var e=a[t];o[t]={navigationOffset:{below:e.options.offset.below||0,above:e.options.offset.above||0},axisMin:e.min,axisMax:e.max,diagMode:!1}}),o.startPageX=t||0,o.startPageY=e||0,o},e.activate=function(){var t=e.getOptions();t.pan.active&&t.zoom.active||(t.pan.active=!0,t.zoom.active=!0,e.getPlaceholder().trigger("plotactivated",[e]))},e.zoomOut=function(t){t||(t={}),t.amount||(t.amount=e.getOptions().zoom.amount),t.amount=1/t.amount,e.zoom(t)},e.zoom=function(a){a||(a={});var o=a.center,n=a.amount||e.getOptions().zoom.amount,i=e.width(),r=e.height(),s=a.axes||e.getAxes();o||(o={left:i/2,top:r/2});var f=o.left/i,l=o.top/r,u={x:{min:o.left-f*i/n,max:o.left+(1-f)*i/n},y:{min:o.top-l*r/n,max:o.top+(1-l)*r/n}};for(var c in s)if(s.hasOwnProperty(c)){var p=s[c],d=p.options,g=u[p.direction].min,x=u[p.direction].max,v=p.options.offset;if((d.axisZoom||!a.axes)&&(a.axes||d.plotZoom)){if(g=t.plot.saturated.saturate(p.c2p(g)),x=t.plot.saturated.saturate(p.c2p(x)),g>x){var m=g;g=x,x=m}var h=t.plot.saturated.saturate(v.below-(p.min-g)),b=t.plot.saturated.saturate(v.above-(p.max-x));d.offset={below:h,above:b}}}e.setupGrid(!0),e.draw(),a.preventEvent||e.getPlaceholder().trigger("plotzoom",[e,a])},e.pan=function(o){var n={x:+o.left,y:+o.top};isNaN(n.x)&&(n.x=0),isNaN(n.y)&&(n.y=0),t.each(o.axes||e.getAxes(),function(t,e){var i=e.options,r=n[e.direction];if((i.axisPan||!o.axes)&&(i.plotPan||o.axes)&&0!==r){var s=a.saturate(e.c2p(e.p2c(e.min)+r)-e.c2p(e.p2c(e.min))),f=a.saturate(e.c2p(e.p2c(e.max)+r)-e.c2p(e.p2c(e.max)));isFinite(s)||(s=0),isFinite(f)||(f=0),i.offset={below:a.saturate(s+(i.offset.below||0)),above:a.saturate(f+(i.offset.above||0))}}}),e.setupGrid(!0),e.draw(),o.preventEvent||e.getPlaceholder().trigger("plotpan",[e,o])},e.recenter=function(a){t.each(a.axes||e.getAxes(),function(t,e){a.axes?"x"===this.direction?e.options.offset={below:0}:"y"===this.direction&&(e.options.offset={above:0}):e.options.offset={below:0,above:0}}),e.setupGrid(!0),e.draw()};var A=function(t){return Math.abs(t.y)<n&&Math.abs(t.x)>=n||Math.abs(t.x)<n&&Math.abs(t.y)>=n},N=function(t){return Math.abs(t.x)<n&&Math.abs(t.y)>=n?{x:0,y:t.y}:Math.abs(t.y)<n&&Math.abs(t.x)>=n?{x:t.x,y:0}:t},k=null,E=function(t){switch(!k&&Math.max(Math.abs(t.x),Math.abs(t.y))>=n&&(k=Math.abs(t.x)<Math.abs(t.y)?"y":"x"),k){case"x":return{x:t.x,y:0};case"y":return{x:0,y:t.y};default:return{x:0,y:0}}},z=function(t){return Math.abs(t.x)>0&&Math.abs(t.y)>0},G=function(t,e,a){var o;Object.keys(t).forEach(function(n){o=t[n],0===a[o.direction]&&(o.options.offset.below=e[n].navigationOffset.below,o.options.offset.above=e[n].navigationOffset.above)})},S={x:0,y:0};e.smartPan=function(t,o,n,i,r){var s,f=!!r||A(t),l=e.getAxes();t=r?E(t):N(t),z(t)&&(o.diagMode=!0),f&&!0===o.diagMode&&(o.diagMode=!1,G(l,o,t)),Y=f?{start:{x:o.startPageX-e.offset().left+e.getPlotOffset().left,y:o.startPageY-e.offset().top+e.getPlotOffset().top},end:{x:o.startPageX-t.x-e.offset().left+e.getPlotOffset().left,y:o.startPageY-t.y-e.offset().top+e.getPlotOffset().top}}:{start:{x:o.startPageX-e.offset().left+e.getPlotOffset().left,y:o.startPageY-e.offset().top+e.getPlotOffset().top},end:!1},isNaN(t.x)&&(t.x=0),isNaN(t.y)&&(t.y=0),n&&(l=n);var u,c,p,d,g;Object.keys(l).forEach(function(e){if(u=l[e],c=u.min,p=u.max,s=u.options,g=t[u.direction],d=S[u.direction],(s.axisPan||!n)&&(n||s.plotPan)&&0!==g){var o=a.saturate(u.c2p(u.p2c(c)-(d-g))-u.c2p(u.p2c(c))),i=a.saturate(u.c2p(u.p2c(p)-(d-g))-u.c2p(u.p2c(p)));isFinite(o)||(o=0),isFinite(i)||(i=0),u.options.offset.below=a.saturate(o+(u.options.offset.below||0)),u.options.offset.above=a.saturate(i+(u.options.offset.above||0))}}),S=t,e.setupGrid(!0),e.draw(),i||e.getPlaceholder().trigger("plotpan",[e,t,n,o])},e.smartPan.end=function(){Y=null,k=null,S={x:0,y:0},e.triggerRedrawOverlay()},e.getTouchedAxis=function(t,a){var o=e.getPlaceholder().offset();return o.left=t-o.left,o.top=a-o.top,e.getXAxes().concat(e.getYAxes()).filter(function(t){var e=t.box;if(void 0!==e)return o.left>e.left&&o.left<e.left+e.width&&o.top>e.top&&o.top<e.top+e.height})},e.hooks.drawOverlay.push(function(t,e){if(Y){e.strokeStyle="rgba(96, 160, 208, 0.7)",e.lineWidth=2,e.lineJoin="round";var a,o,n=Math.round(Y.start.x),r=Math.round(Y.start.y);if(h?"x"===h[0].direction?(o=Math.round(Y.start.y),a=Math.round(Y.end.x)):"y"===h[0].direction&&(a=Math.round(Y.start.x),o=Math.round(Y.end.y)):(a=Math.round(Y.end.x),o=Math.round(Y.end.y)),e.beginPath(),!1===Y.end)e.moveTo(n,r-i),e.lineTo(n,r+i),e.moveTo(n+i,r),e.lineTo(n-i,r);else{var s=r===o;e.moveTo(n-(s?0:i),r-(s?i:0)),e.lineTo(n+(s?0:i),r+(s?i:0)),e.moveTo(n,r),e.lineTo(a,o),e.moveTo(a-(s?0:i),o-(s?i:0)),e.lineTo(a+(s?0:i),o+(s?i:0))}e.stroke()}}),e.hooks.bindEvents.push(function(t,e){var a=t.getOptions();a.zoom.interactive&&e.mousewheel(f),a.pan.interactive&&(t.addEventHandler("dragstart",p,e,0),t.addEventHandler("drag",d,e,0),t.addEventHandler("dragend",g,e,0),e.bind("mousedown",l),e.bind("mouseup",u)),e.dblclick(x),e.click(v)}),e.hooks.shutdown.push(function(t,e){e.unbind("mousewheel",f),e.unbind("mousedown",l),e.unbind("mouseup",u),e.unbind("dragstart",p),e.unbind("drag",d),e.unbind("dragend",g),e.unbind("dblclick",x),e.unbind("click",v),O&&clearTimeout(O)})}var a=t.plot.saturated,o=t.plot.browser,n=t.plot.uiConstants.SNAPPING_CONSTANT,i=t.plot.uiConstants.PANHINT_LENGTH_CONSTANT;t.plot.plugins.push({init:function(t){t.hooks.processOptions.push(e)},options:{zoom:{interactive:!1,active:!1,amount:1.5},pan:{interactive:!1,active:!1,cursor:"move",frameRate:60,mode:"smart"},recenter:{interactive:!0},xaxis:{axisZoom:!0,plotZoom:!0,axisPan:!0,plotPan:!0},yaxis:{axisZoom:!0,plotZoom:!0,axisPan:!0,plotPan:!0}},name:"navigate",version:"1.3"})}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.pie.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.pie.js	Wed Oct 16 13:00:43 2019 +0200
@@ -15,806 +15,772 @@
 
 The plugin supports these options:
 
-	series: {
-		pie: {
-			show: true/false
-			radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
-			innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
-			startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
-			tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
-			offset: {
-				top: integer value to move the pie up or down
-				left: integer value to move the pie left or right, or 'auto'
-			},
-			stroke: {
-				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
-				width: integer pixel width of the stroke
-			},
-			label: {
-				show: true/false, or 'auto'
-				formatter:  a user-defined function that modifies the text/style of the label text
-				radius: 0-1 for percentage of fullsize, or a specified pixel length
-				background: {
-					color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
-					opacity: 0-1
-				},
-				threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
-			},
-			combine: {
-				threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
-				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
-				label: any text value of what the combined slice should be labeled
-			}
-			highlight: {
-				opacity: 0-1
-			}
-		}
-	}
+    series: {
+        pie: {
+            show: true/false
+            radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
+            innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
+            startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
+            tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
+            offset: {
+                top: integer value to move the pie up or down
+                left: integer value to move the pie left or right, or 'auto'
+            },
+            stroke: {
+                color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
+                width: integer pixel width of the stroke
+            },
+            label: {
+                show: true/false, or 'auto'
+                formatter:  a user-defined function that modifies the text/style of the label text
+                radius: 0-1 for percentage of fullsize, or a specified pixel length
+                background: {
+                    color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
+                    opacity: 0-1
+                },
+                threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
+            },
+            combine: {
+                threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
+                color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
+                label: any text value of what the combined slice should be labeled
+            }
+            highlight: {
+                opacity: 0-1
+            }
+        }
+    }
 
 More detail and specific examples can be found in the included HTML file.
 
 */
 
 (function($) {
+    // Maximum redraw attempts when fitting labels within the plot
 
-	// Maximum redraw attempts when fitting labels within the plot
+    var REDRAW_ATTEMPTS = 10;
 
-	var REDRAW_ATTEMPTS = 10;
+    // Factor by which to shrink the pie when fitting labels within the plot
 
-	// Factor by which to shrink the pie when fitting labels within the plot
-
-	var REDRAW_SHRINK = 0.95;
+    var REDRAW_SHRINK = 0.95;
 
-	function init(plot) {
+    function init(plot) {
+        var canvas = null,
+            target = null,
+            options = null,
+            maxRadius = null,
+            centerLeft = null,
+            centerTop = null,
+            processed = false,
+            ctx = null;
 
-		var canvas = null,
-			target = null,
-			options = null,
-			maxRadius = null,
-			centerLeft = null,
-			centerTop = null,
-			processed = false,
-			ctx = null;
+        // interactive variables
 
-		// interactive variables
+        var highlights = [];
 
-		var highlights = [];
+        // add hook to determine if pie plugin in enabled, and then perform necessary operations
 
-		// add hook to determine if pie plugin in enabled, and then perform necessary operations
+        plot.hooks.processOptions.push(function(plot, options) {
+            if (options.series.pie.show) {
+                options.grid.show = false;
 
-		plot.hooks.processOptions.push(function(plot, options) {
-			if (options.series.pie.show) {
-
-				options.grid.show = false;
+                // set labels.show
 
-				// set labels.show
+                if (options.series.pie.label.show === "auto") {
+                    if (options.legend.show) {
+                        options.series.pie.label.show = false;
+                    } else {
+                        options.series.pie.label.show = true;
+                    }
+                }
 
-				if (options.series.pie.label.show == "auto") {
-					if (options.legend.show) {
-						options.series.pie.label.show = false;
-					} else {
-						options.series.pie.label.show = true;
-					}
-				}
-
-				// set radius
+                // set radius
 
-				if (options.series.pie.radius == "auto") {
-					if (options.series.pie.label.show) {
-						options.series.pie.radius = 3/4;
-					} else {
-						options.series.pie.radius = 1;
-					}
-				}
+                if (options.series.pie.radius === "auto") {
+                    if (options.series.pie.label.show) {
+                        options.series.pie.radius = 3 / 4;
+                    } else {
+                        options.series.pie.radius = 1;
+                    }
+                }
 
-				// ensure sane tilt
+                // ensure sane tilt
 
-				if (options.series.pie.tilt > 1) {
-					options.series.pie.tilt = 1;
-				} else if (options.series.pie.tilt < 0) {
-					options.series.pie.tilt = 0;
-				}
-			}
-		});
+                if (options.series.pie.tilt > 1) {
+                    options.series.pie.tilt = 1;
+                } else if (options.series.pie.tilt < 0) {
+                    options.series.pie.tilt = 0;
+                }
+            }
+        });
 
-		plot.hooks.bindEvents.push(function(plot, eventHolder) {
-			var options = plot.getOptions();
-			if (options.series.pie.show) {
-				if (options.grid.hoverable) {
-					eventHolder.unbind("mousemove").mousemove(onMouseMove);
-				}
-				if (options.grid.clickable) {
-					eventHolder.unbind("click").click(onClick);
-				}
-			}
-		});
+        plot.hooks.bindEvents.push(function(plot, eventHolder) {
+            var options = plot.getOptions();
+            if (options.series.pie.show) {
+                if (options.grid.hoverable) {
+                    eventHolder.unbind("mousemove").mousemove(onMouseMove);
+                }
+                if (options.grid.clickable) {
+                    eventHolder.unbind("click").click(onClick);
+                }
+            }
+        });
 
-		plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
-			var options = plot.getOptions();
-			if (options.series.pie.show) {
-				processDatapoints(plot, series, data, datapoints);
-			}
-		});
+        plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
+            var options = plot.getOptions();
+            if (options.series.pie.show) {
+                processDatapoints(plot, series, data, datapoints);
+            }
+        });
 
-		plot.hooks.drawOverlay.push(function(plot, octx) {
-			var options = plot.getOptions();
-			if (options.series.pie.show) {
-				drawOverlay(plot, octx);
-			}
-		});
+        plot.hooks.drawOverlay.push(function(plot, octx) {
+            var options = plot.getOptions();
+            if (options.series.pie.show) {
+                drawOverlay(plot, octx);
+            }
+        });
 
-		plot.hooks.draw.push(function(plot, newCtx) {
-			var options = plot.getOptions();
-			if (options.series.pie.show) {
-				draw(plot, newCtx);
-			}
-		});
+        plot.hooks.draw.push(function(plot, newCtx) {
+            var options = plot.getOptions();
+            if (options.series.pie.show) {
+                draw(plot, newCtx);
+            }
+        });
 
-		function processDatapoints(plot, series, datapoints) {
-			if (!processed)	{
-				processed = true;
-				canvas = plot.getCanvas();
-				target = $(canvas).parent();
-				options = plot.getOptions();
-				plot.setData(combine(plot.getData()));
-			}
-		}
-
-		function combine(data) {
+        function processDatapoints(plot, series, datapoints) {
+            if (!processed) {
+                processed = true;
+                canvas = plot.getCanvas();
+                target = $(canvas).parent();
+                options = plot.getOptions();
+                plot.setData(combine(plot.getData()));
+            }
+        }
 
-			var total = 0,
-				combined = 0,
-				numCombined = 0,
-				color = options.series.pie.combine.color,
-				newdata = [];
+        function combine(data) {
+            var total = 0,
+                combined = 0,
+                numCombined = 0,
+                color = options.series.pie.combine.color,
+                newdata = [],
+                i,
+                value;
 
-			// Fix up the raw data from Flot, ensuring the data is numeric
-
-			for (var i = 0; i < data.length; ++i) {
+            // Fix up the raw data from Flot, ensuring the data is numeric
 
-				var value = data[i].data;
+            for (i = 0; i < data.length; ++i) {
+                value = data[i].data;
 
-				// If the data is an array, we'll assume that it's a standard
-				// Flot x-y pair, and are concerned only with the second value.
+                // If the data is an array, we'll assume that it's a standard
+                // Flot x-y pair, and are concerned only with the second value.
 
-				// Note how we use the original array, rather than creating a
-				// new one; this is more efficient and preserves any extra data
-				// that the user may have stored in higher indexes.
+                // Note how we use the original array, rather than creating a
+                // new one; this is more efficient and preserves any extra data
+                // that the user may have stored in higher indexes.
 
-				if ($.isArray(value) && value.length == 1) {
-    				value = value[0];
-				}
+                if ($.isArray(value) && value.length === 1) {
+                    value = value[0];
+                }
 
-				if ($.isArray(value)) {
-					// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
-					if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
-						value[1] = +value[1];
-					} else {
-						value[1] = 0;
-					}
-				} else if (!isNaN(parseFloat(value)) && isFinite(value)) {
-					value = [1, +value];
-				} else {
-					value = [1, 0];
-				}
+                if ($.isArray(value)) {
+                    // Equivalent to $.isNumeric() but compatible with jQuery < 1.7
+                    if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
+                        value[1] = +value[1];
+                    } else {
+                        value[1] = 0;
+                    }
+                } else if (!isNaN(parseFloat(value)) && isFinite(value)) {
+                    value = [1, +value];
+                } else {
+                    value = [1, 0];
+                }
 
-				data[i].data = [value];
-			}
+                data[i].data = [value];
+            }
 
-			// Sum up all the slices, so we can calculate percentages for each
+            // Sum up all the slices, so we can calculate percentages for each
 
-			for (var i = 0; i < data.length; ++i) {
-				total += data[i].data[0][1];
-			}
+            for (i = 0; i < data.length; ++i) {
+                total += data[i].data[0][1];
+            }
 
-			// Count the number of slices with percentages below the combine
-			// threshold; if it turns out to be just one, we won't combine.
+            // Count the number of slices with percentages below the combine
+            // threshold; if it turns out to be just one, we won't combine.
 
-			for (var i = 0; i < data.length; ++i) {
-				var value = data[i].data[0][1];
-				if (value / total <= options.series.pie.combine.threshold) {
-					combined += value;
-					numCombined++;
-					if (!color) {
-						color = data[i].color;
-					}
-				}
-			}
+            for (i = 0; i < data.length; ++i) {
+                value = data[i].data[0][1];
+                if (value / total <= options.series.pie.combine.threshold) {
+                    combined += value;
+                    numCombined++;
+                    if (!color) {
+                        color = data[i].color;
+                    }
+                }
+            }
 
-			for (var i = 0; i < data.length; ++i) {
-				var value = data[i].data[0][1];
-				if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
-					newdata.push(
-						$.extend(data[i], {     /* extend to allow keeping all other original data values
-						                           and using them e.g. in labelFormatter. */
-							data: [[1, value]],
-							color: data[i].color,
-							label: data[i].label,
-							angle: value * Math.PI * 2 / total,
-							percent: value / (total / 100)
-						})
-					);
-				}
-			}
+            for (i = 0; i < data.length; ++i) {
+                value = data[i].data[0][1];
+                if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
+                    newdata.push(
+                        $.extend(data[i], {     /* extend to allow keeping all other original data values
+                                                   and using them e.g. in labelFormatter. */
+                            data: [[1, value]],
+                            color: data[i].color,
+                            label: data[i].label,
+                            angle: value * Math.PI * 2 / total,
+                            percent: value / (total / 100)
+                        })
+                    );
+                }
+            }
 
-			if (numCombined > 1) {
-				newdata.push({
-					data: [[1, combined]],
-					color: color,
-					label: options.series.pie.combine.label,
-					angle: combined * Math.PI * 2 / total,
-					percent: combined / (total / 100)
-				});
-			}
+            if (numCombined > 1) {
+                newdata.push({
+                    data: [[1, combined]],
+                    color: color,
+                    label: options.series.pie.combine.label,
+                    angle: combined * Math.PI * 2 / total,
+                    percent: combined / (total / 100)
+                });
+            }
 
-			return newdata;
-		}
-
-		function draw(plot, newCtx) {
+            return newdata;
+        }
 
-			if (!target) {
-				return; // if no series were passed
-			}
+        function draw(plot, newCtx) {
+            if (!target) {
+                return; // if no series were passed
+            }
 
-			var canvasWidth = plot.getPlaceholder().width(),
-				canvasHeight = plot.getPlaceholder().height(),
-				legendWidth = target.children().filter(".legend").children().width() || 0;
+            var canvasWidth = plot.getPlaceholder().width(),
+                canvasHeight = plot.getPlaceholder().height(),
+                legendWidth = target.children().filter(".legend").children().width() || 0;
 
-			ctx = newCtx;
+            ctx = newCtx;
 
-			// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
+            // WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!
 
-			// When combining smaller slices into an 'other' slice, we need to
-			// add a new series.  Since Flot gives plugins no way to modify the
-			// list of series, the pie plugin uses a hack where the first call
-			// to processDatapoints results in a call to setData with the new
-			// list of series, then subsequent processDatapoints do nothing.
+            // When combining smaller slices into an 'other' slice, we need to
+            // add a new series.  Since Flot gives plugins no way to modify the
+            // list of series, the pie plugin uses a hack where the first call
+            // to processDatapoints results in a call to setData with the new
+            // list of series, then subsequent processDatapoints do nothing.
 
-			// The plugin-global 'processed' flag is used to control this hack;
-			// it starts out false, and is set to true after the first call to
-			// processDatapoints.
-
-			// Unfortunately this turns future setData calls into no-ops; they
-			// call processDatapoints, the flag is true, and nothing happens.
+            // The plugin-global 'processed' flag is used to control this hack;
+            // it starts out false, and is set to true after the first call to
+            // processDatapoints.
 
-			// To fix this we'll set the flag back to false here in draw, when
-			// all series have been processed, so the next sequence of calls to
-			// processDatapoints once again starts out with a slice-combine.
-			// This is really a hack; in 0.9 we need to give plugins a proper
-			// way to modify series before any processing begins.
+            // Unfortunately this turns future setData calls into no-ops; they
+            // call processDatapoints, the flag is true, and nothing happens.
 
-			processed = false;
+            // To fix this we'll set the flag back to false here in draw, when
+            // all series have been processed, so the next sequence of calls to
+            // processDatapoints once again starts out with a slice-combine.
+            // This is really a hack; in 0.9 we need to give plugins a proper
+            // way to modify series before any processing begins.
 
-			// calculate maximum radius and center point
+            processed = false;
 
-			maxRadius =  Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
-			centerTop = canvasHeight / 2 + options.series.pie.offset.top;
-			centerLeft = canvasWidth / 2;
+            // calculate maximum radius and center point
+            maxRadius = Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
+            centerTop = canvasHeight / 2 + options.series.pie.offset.top;
+            centerLeft = canvasWidth / 2;
 
-			if (options.series.pie.offset.left == "auto") {
-				if (options.legend.position.match("w")) {
-					centerLeft += legendWidth / 2;
-				} else {
-					centerLeft -= legendWidth / 2;
-				}
-				if (centerLeft < maxRadius) {
-					centerLeft = maxRadius;
-				} else if (centerLeft > canvasWidth - maxRadius) {
-					centerLeft = canvasWidth - maxRadius;
-				}
-			} else {
-				centerLeft += options.series.pie.offset.left;
-			}
-
-			var slices = plot.getData(),
-				attempts = 0;
+            if (options.series.pie.offset.left === "auto") {
+                if (options.legend.position.match("w")) {
+                    centerLeft += legendWidth / 2;
+                } else {
+                    centerLeft -= legendWidth / 2;
+                }
+                if (centerLeft < maxRadius) {
+                    centerLeft = maxRadius;
+                } else if (centerLeft > canvasWidth - maxRadius) {
+                    centerLeft = canvasWidth - maxRadius;
+                }
+            } else {
+                centerLeft += options.series.pie.offset.left;
+            }
 
-			// Keep shrinking the pie's radius until drawPie returns true,
-			// indicating that all the labels fit, or we try too many times.
+            var slices = plot.getData(),
+                attempts = 0;
 
-			do {
-				if (attempts > 0) {
-					maxRadius *= REDRAW_SHRINK;
-				}
-				attempts += 1;
-				clear();
-				if (options.series.pie.tilt <= 0.8) {
-					drawShadow();
-				}
-			} while (!drawPie() && attempts < REDRAW_ATTEMPTS)
+            // Keep shrinking the pie's radius until drawPie returns true,
+            // indicating that all the labels fit, or we try too many times.
+            do {
+                if (attempts > 0) {
+                    maxRadius *= REDRAW_SHRINK;
+                }
+                attempts += 1;
+                clear();
+                if (options.series.pie.tilt <= 0.8) {
+                    drawShadow();
+                }
+            } while (!drawPie() && attempts < REDRAW_ATTEMPTS)
 
-			if (attempts >= REDRAW_ATTEMPTS) {
-				clear();
-				target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
-			}
+            if (attempts >= REDRAW_ATTEMPTS) {
+                clear();
+                target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
+            }
 
-			if (plot.setSeries && plot.insertLegend) {
-				plot.setSeries(slices);
-				plot.insertLegend();
-			}
-
-			// we're actually done at this point, just defining internal functions at this point
+            if (plot.setSeries && plot.insertLegend) {
+                plot.setSeries(slices);
+                plot.insertLegend();
+            }
 
-			function clear() {
-				ctx.clearRect(0, 0, canvasWidth, canvasHeight);
-				target.children().filter(".pieLabel, .pieLabelBackground").remove();
-			}
-
-			function drawShadow() {
+            // we're actually done at this point, just defining internal functions at this point
+            function clear() {
+                ctx.clearRect(0, 0, canvasWidth, canvasHeight);
+                target.children().filter(".pieLabel, .pieLabelBackground").remove();
+            }
 
-				var shadowLeft = options.series.pie.shadow.left;
-				var shadowTop = options.series.pie.shadow.top;
-				var edge = 10;
-				var alpha = options.series.pie.shadow.alpha;
-				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+            function drawShadow() {
+                var shadowLeft = options.series.pie.shadow.left;
+                var shadowTop = options.series.pie.shadow.top;
+                var edge = 10;
+                var alpha = options.series.pie.shadow.alpha;
+                var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
 
-				if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
-					return;	// shadow would be outside canvas, so don't draw it
-				}
-
-				ctx.save();
-				ctx.translate(shadowLeft,shadowTop);
-				ctx.globalAlpha = alpha;
-				ctx.fillStyle = "#000";
+                if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
+                    return;    // shadow would be outside canvas, so don't draw it
+                }
 
-				// center and rotate to starting position
+                ctx.save();
+                ctx.translate(shadowLeft, shadowTop);
+                ctx.globalAlpha = alpha;
+                ctx.fillStyle = "#000";
 
-				ctx.translate(centerLeft,centerTop);
-				ctx.scale(1, options.series.pie.tilt);
-
-				//radius -= edge;
+                // center and rotate to starting position
+                ctx.translate(centerLeft, centerTop);
+                ctx.scale(1, options.series.pie.tilt);
 
-				for (var i = 1; i <= edge; i++) {
-					ctx.beginPath();
-					ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
-					ctx.fill();
-					radius -= i;
-				}
+                //radius -= edge;
+                for (var i = 1; i <= edge; i++) {
+                    ctx.beginPath();
+                    ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
+                    ctx.fill();
+                    radius -= i;
+                }
 
-				ctx.restore();
-			}
-
-			function drawPie() {
+                ctx.restore();
+            }
 
-				var startAngle = Math.PI * options.series.pie.startAngle;
-				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
-
-				// center and rotate to starting position
+            function drawPie() {
+                var startAngle = Math.PI * options.series.pie.startAngle;
+                var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+                var i;
+                // center and rotate to starting position
 
-				ctx.save();
-				ctx.translate(centerLeft,centerTop);
-				ctx.scale(1, options.series.pie.tilt);
-				//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
+                ctx.save();
+                ctx.translate(centerLeft, centerTop);
+                ctx.scale(1, options.series.pie.tilt);
+                //ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera
 
-				// draw slices
+                // draw slices
+                ctx.save();
 
-				ctx.save();
-				var currentAngle = startAngle;
-				for (var i = 0; i < slices.length; ++i) {
-					slices[i].startAngle = currentAngle;
-					drawSlice(slices[i].angle, slices[i].color, true);
-				}
-				ctx.restore();
+                var currentAngle = startAngle;
+                for (i = 0; i < slices.length; ++i) {
+                    slices[i].startAngle = currentAngle;
+                    drawSlice(slices[i].angle, slices[i].color, true);
+                }
+
+                ctx.restore();
 
-				// draw slice outlines
+                // draw slice outlines
+                if (options.series.pie.stroke.width > 0) {
+                    ctx.save();
+                    ctx.lineWidth = options.series.pie.stroke.width;
+                    currentAngle = startAngle;
+                    for (i = 0; i < slices.length; ++i) {
+                        drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
+                    }
 
-				if (options.series.pie.stroke.width > 0) {
-					ctx.save();
-					ctx.lineWidth = options.series.pie.stroke.width;
-					currentAngle = startAngle;
-					for (var i = 0; i < slices.length; ++i) {
-						drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
-					}
-					ctx.restore();
-				}
+                    ctx.restore();
+                }
 
-				// draw donut hole
+                // draw donut hole
+                drawDonutHole(ctx);
 
-				drawDonutHole(ctx);
-
-				ctx.restore();
+                ctx.restore();
 
-				// Draw the labels, returning true if they fit within the plot
-
-				if (options.series.pie.label.show) {
-					return drawLabels();
-				} else return true;
-
-				function drawSlice(angle, color, fill) {
+                // Draw the labels, returning true if they fit within the plot
+                if (options.series.pie.label.show) {
+                    return drawLabels();
+                } else return true;
 
-					if (angle <= 0 || isNaN(angle)) {
-						return;
-					}
+                function drawSlice(angle, color, fill) {
+                    if (angle <= 0 || isNaN(angle)) {
+                        return;
+                    }
 
-					if (fill) {
-						ctx.fillStyle = color;
-					} else {
-						ctx.strokeStyle = color;
-						ctx.lineJoin = "round";
-					}
+                    if (fill) {
+                        ctx.fillStyle = color;
+                    } else {
+                        ctx.strokeStyle = color;
+                        ctx.lineJoin = "round";
+                    }
 
-					ctx.beginPath();
-					if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
-						ctx.moveTo(0, 0); // Center of the pie
-					}
+                    ctx.beginPath();
+                    if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
+                        ctx.moveTo(0, 0); // Center of the pie
+                    }
 
-					//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
-					ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
-					ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
-					ctx.closePath();
-					//ctx.rotate(angle); // This doesn't work properly in Opera
-					currentAngle += angle;
+                    //ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
+                    ctx.arc(0, 0, radius, currentAngle, currentAngle + angle / 2, false);
+                    ctx.arc(0, 0, radius, currentAngle + angle / 2, currentAngle + angle, false);
+                    ctx.closePath();
+                    //ctx.rotate(angle); // This doesn't work properly in Opera
+                    currentAngle += angle;
 
-					if (fill) {
-						ctx.fill();
-					} else {
-						ctx.stroke();
-					}
-				}
-
-				function drawLabels() {
+                    if (fill) {
+                        ctx.fill();
+                    } else {
+                        ctx.stroke();
+                    }
+                }
 
-					var currentAngle = startAngle;
-					var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
-
-					for (var i = 0; i < slices.length; ++i) {
-						if (slices[i].percent >= options.series.pie.label.threshold * 100) {
-							if (!drawLabel(slices[i], currentAngle, i)) {
-								return false;
-							}
-						}
-						currentAngle += slices[i].angle;
-					}
+                function drawLabels() {
+                    var currentAngle = startAngle;
+                    var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;
 
-					return true;
-
-					function drawLabel(slice, startAngle, index) {
+                    for (var i = 0; i < slices.length; ++i) {
+                        if (slices[i].percent >= options.series.pie.label.threshold * 100) {
+                            if (!drawLabel(slices[i], currentAngle, i)) {
+                                return false;
+                            }
+                        }
+                        currentAngle += slices[i].angle;
+                    }
 
-						if (slice.data[0][1] == 0) {
-							return true;
-						}
+                    return true;
 
-						// format label text
+                    function drawLabel(slice, startAngle, index) {
+                        if (slice.data[0][1] === 0) {
+                            return true;
+                        }
 
-						var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
+                        // format label text
+                        var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;
 
-						if (lf) {
-							text = lf(slice.label, slice);
-						} else {
-							text = slice.label;
-						}
+                        if (lf) {
+                            text = lf(slice.label, slice);
+                        } else {
+                            text = slice.label;
+                        }
 
-						if (plf) {
-							text = plf(text, slice);
-						}
+                        if (plf) {
+                            text = plf(text, slice);
+                        }
 
-						var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
-						var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
-						var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
+                        var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
+                        var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
+                        var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;
 
-						var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
-						target.append(html);
+                        var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
+                        target.append(html);
 
-						var label = target.children("#pieLabel" + index);
-						var labelTop = (y - label.height() / 2);
-						var labelLeft = (x - label.width() / 2);
+                        var label = target.children("#pieLabel" + index);
+                        var labelTop = (y - label.height() / 2);
+                        var labelLeft = (x - label.width() / 2);
 
-						label.css("top", labelTop);
-						label.css("left", labelLeft);
-
-						// check to make sure that the label is not outside the canvas
+                        label.css("top", labelTop);
+                        label.css("left", labelLeft);
 
-						if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
-							return false;
-						}
-
-						if (options.series.pie.label.background.opacity != 0) {
+                        // check to make sure that the label is not outside the canvas
+                        if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
+                            return false;
+                        }
 
-							// put in the transparent background separately to avoid blended labels and label boxes
-
-							var c = options.series.pie.label.background.color;
+                        if (options.series.pie.label.background.opacity !== 0) {
+                            // put in the transparent background separately to avoid blended labels and label boxes
+                            var c = options.series.pie.label.background.color;
+                            if (c == null) {
+                                c = slice.color;
+                            }
 
-							if (c == null) {
-								c = slice.color;
-							}
-
-							var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
-							$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
-								.css("opacity", options.series.pie.label.background.opacity)
-								.insertBefore(label);
-						}
+                            var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
+                            $("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
+                                .css("opacity", options.series.pie.label.background.opacity)
+                                .insertBefore(label);
+                        }
 
-						return true;
-					} // end individual label function
-				} // end drawLabels function
-			} // end drawPie function
-		} // end draw function
+                        return true;
+                    } // end individual label function
+                } // end drawLabels function
+            } // end drawPie function
+        } // end draw function
 
-		// Placed here because it needs to be accessed from multiple locations
-
-		function drawDonutHole(layer) {
-			if (options.series.pie.innerRadius > 0) {
+        // Placed here because it needs to be accessed from multiple locations
 
-				// subtract the center
-
-				layer.save();
-				var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
-				layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
-				layer.beginPath();
-				layer.fillStyle = options.series.pie.stroke.color;
-				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
-				layer.fill();
-				layer.closePath();
-				layer.restore();
+        function drawDonutHole(layer) {
+            if (options.series.pie.innerRadius > 0) {
+                // subtract the center
+                layer.save();
+                var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
+                layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
+                layer.beginPath();
+                layer.fillStyle = options.series.pie.stroke.color;
+                layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+                layer.fill();
+                layer.closePath();
+                layer.restore();
 
-				// add inner stroke
-
-				layer.save();
-				layer.beginPath();
-				layer.strokeStyle = options.series.pie.stroke.color;
-				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
-				layer.stroke();
-				layer.closePath();
-				layer.restore();
+                // add inner stroke
+                layer.save();
+                layer.beginPath();
+                layer.strokeStyle = options.series.pie.stroke.color;
+                layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
+                layer.stroke();
+                layer.closePath();
+                layer.restore();
 
-				// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
-			}
-		}
+                // TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
+            }
+        }
 
-		//-- Additional Interactive related functions --
+        //-- Additional Interactive related functions --
 
-		function isPointInPoly(poly, pt) {
-			for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
-				((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
-				&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
-				&& (c = !c);
-			return c;
-		}
-
-		function findNearbySlice(mouseX, mouseY) {
+        function isPointInPoly(poly, pt) {
+            for (var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i) {
+                ((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) ||
+                (poly[j][1] <= pt[1] && pt[1] < poly[i][1])) &&
+                (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0]) &&
+                (c = !c);
+            }
+            return c;
+        }
 
-			var slices = plot.getData(),
-				options = plot.getOptions(),
-				radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
-				x, y;
-
-			for (var i = 0; i < slices.length; ++i) {
-
-				var s = slices[i];
+        function findNearbySlice(mouseX, mouseY) {
+            var slices = plot.getData(),
+                options = plot.getOptions(),
+                radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
+                x, y;
 
-				if (s.pie.show) {
-
-					ctx.save();
-					ctx.beginPath();
-					ctx.moveTo(0, 0); // Center of the pie
-					//ctx.scale(1, options.series.pie.tilt);	// this actually seems to break everything when here.
-					ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
-					ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
-					ctx.closePath();
-					x = mouseX - centerLeft;
-					y = mouseY - centerTop;
+            for (var i = 0; i < slices.length; ++i) {
+                var s = slices[i];
+                if (s.pie.show) {
+                    ctx.save();
+                    ctx.beginPath();
+                    ctx.moveTo(0, 0); // Center of the pie
+                    //ctx.scale(1, options.series.pie.tilt);    // this actually seems to break everything when here.
+                    ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
+                    ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
+                    ctx.closePath();
+                    x = mouseX - centerLeft;
+                    y = mouseY - centerTop;
 
-					if (ctx.isPointInPath) {
-						if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
-							ctx.restore();
-							return {
-								datapoint: [s.percent, s.data],
-								dataIndex: 0,
-								series: s,
-								seriesIndex: i
-							};
-						}
-					} else {
-
-						// excanvas for IE doesn;t support isPointInPath, this is a workaround.
+                    if (ctx.isPointInPath) {
+                        if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
+                            ctx.restore();
+                            return {
+                                datapoint: [s.percent, s.data],
+                                dataIndex: 0,
+                                series: s,
+                                seriesIndex: i
+                            };
+                        }
+                    } else {
+                        // excanvas for IE doesn;t support isPointInPath, this is a workaround.
+                        var p1X = radius * Math.cos(s.startAngle),
+                            p1Y = radius * Math.sin(s.startAngle),
+                            p2X = radius * Math.cos(s.startAngle + s.angle / 4),
+                            p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
+                            p3X = radius * Math.cos(s.startAngle + s.angle / 2),
+                            p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
+                            p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
+                            p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
+                            p5X = radius * Math.cos(s.startAngle + s.angle),
+                            p5Y = radius * Math.sin(s.startAngle + s.angle),
+                            arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
+                            arrPoint = [x, y];
 
-						var p1X = radius * Math.cos(s.startAngle),
-							p1Y = radius * Math.sin(s.startAngle),
-							p2X = radius * Math.cos(s.startAngle + s.angle / 4),
-							p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
-							p3X = radius * Math.cos(s.startAngle + s.angle / 2),
-							p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
-							p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
-							p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
-							p5X = radius * Math.cos(s.startAngle + s.angle),
-							p5Y = radius * Math.sin(s.startAngle + s.angle),
-							arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
-							arrPoint = [x, y];
-
-						// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
+                        // TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?
 
-						if (isPointInPoly(arrPoly, arrPoint)) {
-							ctx.restore();
-							return {
-								datapoint: [s.percent, s.data],
-								dataIndex: 0,
-								series: s,
-								seriesIndex: i
-							};
-						}
-					}
+                        if (isPointInPoly(arrPoly, arrPoint)) {
+                            ctx.restore();
+                            return {
+                                datapoint: [s.percent, s.data],
+                                dataIndex: 0,
+                                series: s,
+                                seriesIndex: i
+                            };
+                        }
+                    }
 
-					ctx.restore();
-				}
-			}
+                    ctx.restore();
+                }
+            }
 
-			return null;
-		}
+            return null;
+        }
+
+        function onMouseMove(e) {
+            triggerClickHoverEvent("plothover", e);
+        }
 
-		function onMouseMove(e) {
-			triggerClickHoverEvent("plothover", e);
-		}
+        function onClick(e) {
+            triggerClickHoverEvent("plotclick", e);
+        }
 
-		function onClick(e) {
-			triggerClickHoverEvent("plotclick", e);
-		}
-
-		// trigger click or hover event (they send the same parameters so we share their code)
+        // trigger click or hover event (they send the same parameters so we share their code)
 
-		function triggerClickHoverEvent(eventname, e) {
+        function triggerClickHoverEvent(eventname, e) {
+            var offset = plot.offset();
+            var canvasX = parseInt(e.pageX - offset.left);
+            var canvasY = parseInt(e.pageY - offset.top);
+            var item = findNearbySlice(canvasX, canvasY);
 
-			var offset = plot.offset();
-			var canvasX = parseInt(e.pageX - offset.left);
-			var canvasY =  parseInt(e.pageY - offset.top);
-			var item = findNearbySlice(canvasX, canvasY);
-
-			if (options.grid.autoHighlight) {
-
-				// clear auto-highlights
+            if (options.grid.autoHighlight) {
+                // clear auto-highlights
+                for (var i = 0; i < highlights.length; ++i) {
+                    var h = highlights[i];
+                    if (h.auto === eventname && !(item && h.series === item.series)) {
+                        unhighlight(h.series);
+                    }
+                }
+            }
 
-				for (var i = 0; i < highlights.length; ++i) {
-					var h = highlights[i];
-					if (h.auto == eventname && !(item && h.series == item.series)) {
-						unhighlight(h.series);
-					}
-				}
-			}
+            // highlight the slice
 
-			// highlight the slice
+            if (item) {
+                highlight(item.series, eventname);
+            }
+
+            // trigger any hover bind events
 
-			if (item) {
-				highlight(item.series, eventname);
-			}
-
-			// trigger any hover bind events
+            var pos = { pageX: e.pageX, pageY: e.pageY };
+            target.trigger(eventname, [pos, item]);
+        }
 
-			var pos = { pageX: e.pageX, pageY: e.pageY };
-			target.trigger(eventname, [pos, item]);
-		}
+        function highlight(s, auto) {
+            //if (typeof s == "number") {
+            //    s = series[s];
+            //}
 
-		function highlight(s, auto) {
-			//if (typeof s == "number") {
-			//	s = series[s];
-			//}
+            var i = indexOfHighlight(s);
 
-			var i = indexOfHighlight(s);
-
-			if (i == -1) {
-				highlights.push({ series: s, auto: auto });
-				plot.triggerRedrawOverlay();
-			} else if (!auto) {
-				highlights[i].auto = false;
-			}
-		}
+            if (i === -1) {
+                highlights.push({ series: s, auto: auto });
+                plot.triggerRedrawOverlay();
+            } else if (!auto) {
+                highlights[i].auto = false;
+            }
+        }
 
-		function unhighlight(s) {
-			if (s == null) {
-				highlights = [];
-				plot.triggerRedrawOverlay();
-			}
+        function unhighlight(s) {
+            if (s == null) {
+                highlights = [];
+                plot.triggerRedrawOverlay();
+            }
 
-			//if (typeof s == "number") {
-			//	s = series[s];
-			//}
+            //if (typeof s == "number") {
+            //    s = series[s];
+            //}
 
-			var i = indexOfHighlight(s);
+            var i = indexOfHighlight(s);
 
-			if (i != -1) {
-				highlights.splice(i, 1);
-				plot.triggerRedrawOverlay();
-			}
-		}
+            if (i !== -1) {
+                highlights.splice(i, 1);
+                plot.triggerRedrawOverlay();
+            }
+        }
 
-		function indexOfHighlight(s) {
-			for (var i = 0; i < highlights.length; ++i) {
-				var h = highlights[i];
-				if (h.series == s)
-					return i;
-			}
-			return -1;
-		}
-
-		function drawOverlay(plot, octx) {
+        function indexOfHighlight(s) {
+            for (var i = 0; i < highlights.length; ++i) {
+                var h = highlights[i];
+                if (h.series === s) {
+                    return i;
+                }
+            }
+            return -1;
+        }
 
-			var options = plot.getOptions();
-
-			var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
+        function drawOverlay(plot, octx) {
+            var options = plot.getOptions();
+            var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;
 
-			octx.save();
-			octx.translate(centerLeft, centerTop);
-			octx.scale(1, options.series.pie.tilt);
+            octx.save();
+            octx.translate(centerLeft, centerTop);
+            octx.scale(1, options.series.pie.tilt);
 
-			for (var i = 0; i < highlights.length; ++i) {
-				drawHighlight(highlights[i].series);
-			}
+            for (var i = 0; i < highlights.length; ++i) {
+                drawHighlight(highlights[i].series);
+            }
 
-			drawDonutHole(octx);
+            drawDonutHole(octx);
 
-			octx.restore();
+            octx.restore();
 
-			function drawHighlight(series) {
-
-				if (series.angle <= 0 || isNaN(series.angle)) {
-					return;
-				}
+            function drawHighlight(series) {
+                if (series.angle <= 0 || isNaN(series.angle)) {
+                    return;
+                }
 
-				//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
-				octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
-				octx.beginPath();
-				if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
-					octx.moveTo(0, 0); // Center of the pie
-				}
-				octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
-				octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
-				octx.closePath();
-				octx.fill();
-			}
-		}
-	} // end init (plugin body)
-
-	// define pie specific options and their default values
+                //octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
+                octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
+                octx.beginPath();
+                if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
+                    octx.moveTo(0, 0); // Center of the pie
+                }
+                octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
+                octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
+                octx.closePath();
+                octx.fill();
+            }
+        }
+    } // end init (plugin body)
 
-	var options = {
-		series: {
-			pie: {
-				show: false,
-				radius: "auto",	// actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
-				innerRadius: 0, /* for donut */
-				startAngle: 3/2,
-				tilt: 1,
-				shadow: {
-					left: 5,	// shadow left offset
-					top: 15,	// shadow top offset
-					alpha: 0.02	// shadow alpha
-				},
-				offset: {
-					top: 0,
-					left: "auto"
-				},
-				stroke: {
-					color: "#fff",
-					width: 1
-				},
-				label: {
-					show: "auto",
-					formatter: function(label, slice) {
-						return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
-					},	// formatter function
-					radius: 1,	// radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
-					background: {
-						color: null,
-						opacity: 0
-					},
-					threshold: 0	// percentage at which to hide the label (i.e. the slice is too narrow)
-				},
-				combine: {
-					threshold: -1,	// percentage at which to combine little slices into one larger slice
-					color: null,	// color to give the new slice (auto-generated if null)
-					label: "Other"	// label to give the new slice
-				},
-				highlight: {
-					//color: "#fff",		// will add this functionality once parseColor is available
-					opacity: 0.5
-				}
-			}
-		}
-	};
+    // define pie specific options and their default values
+    var options = {
+        series: {
+            pie: {
+                show: false,
+                radius: "auto",    // actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
+                innerRadius: 0, /* for donut */
+                startAngle: 3 / 2,
+                tilt: 1,
+                shadow: {
+                    left: 5,    // shadow left offset
+                    top: 15,    // shadow top offset
+                    alpha: 0.02    // shadow alpha
+                },
+                offset: {
+                    top: 0,
+                    left: "auto"
+                },
+                stroke: {
+                    color: "#fff",
+                    width: 1
+                },
+                label: {
+                    show: "auto",
+                    formatter: function(label, slice) {
+                        return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
+                    },    // formatter function
+                    radius: 1,    // radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
+                    background: {
+                        color: null,
+                        opacity: 0
+                    },
+                    threshold: 0    // percentage at which to hide the label (i.e. the slice is too narrow)
+                },
+                combine: {
+                    threshold: -1,    // percentage at which to combine little slices into one larger slice
+                    color: null,    // color to give the new slice (auto-generated if null)
+                    label: "Other"    // label to give the new slice
+                },
+                highlight: {
+                    //color: "#fff",        // will add this functionality once parseColor is available
+                    opacity: 0.5
+                }
+            }
+        }
+    };
 
-	$.plot.plugins.push({
-		init: init,
-		options: options,
-		name: "pie",
-		version: "1.1"
-	});
-
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: "pie",
+        version: "1.1"
+    });
 })(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.pie.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.pie.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,7 +1,1 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
-
-Copyright (c) 2007-2014 IOLA and Ole Laursen.
-Licensed under the MIT license.
-
-*/
-(function($){var REDRAW_ATTEMPTS=10;var REDRAW_SHRINK=.95;function init(plot){var canvas=null,target=null,options=null,maxRadius=null,centerLeft=null,centerTop=null,processed=false,ctx=null;var highlights=[];plot.hooks.processOptions.push(function(plot,options){if(options.series.pie.show){options.grid.show=false;if(options.series.pie.label.show=="auto"){if(options.legend.show){options.series.pie.label.show=false}else{options.series.pie.label.show=true}}if(options.series.pie.radius=="auto"){if(options.series.pie.label.show){options.series.pie.radius=3/4}else{options.series.pie.radius=1}}if(options.series.pie.tilt>1){options.series.pie.tilt=1}else if(options.series.pie.tilt<0){options.series.pie.tilt=0}}});plot.hooks.bindEvents.push(function(plot,eventHolder){var options=plot.getOptions();if(options.series.pie.show){if(options.grid.hoverable){eventHolder.unbind("mousemove").mousemove(onMouseMove)}if(options.grid.clickable){eventHolder.unbind("click").click(onClick)}}});plot.hooks.processDatapoints.push(function(plot,series,data,datapoints){var options=plot.getOptions();if(options.series.pie.show){processDatapoints(plot,series,data,datapoints)}});plot.hooks.drawOverlay.push(function(plot,octx){var options=plot.getOptions();if(options.series.pie.show){drawOverlay(plot,octx)}});plot.hooks.draw.push(function(plot,newCtx){var options=plot.getOptions();if(options.series.pie.show){draw(plot,newCtx)}});function processDatapoints(plot,series,datapoints){if(!processed){processed=true;canvas=plot.getCanvas();target=$(canvas).parent();options=plot.getOptions();plot.setData(combine(plot.getData()))}}function combine(data){var total=0,combined=0,numCombined=0,color=options.series.pie.combine.color,newdata=[];for(var i=0;i<data.length;++i){var value=data[i].data;if($.isArray(value)&&value.length==1){value=value[0]}if($.isArray(value)){if(!isNaN(parseFloat(value[1]))&&isFinite(value[1])){value[1]=+value[1]}else{value[1]=0}}else if(!isNaN(parseFloat(value))&&isFinite(value)){value=[1,+value]}else{value=[1,0]}data[i].data=[value]}for(var i=0;i<data.length;++i){total+=data[i].data[0][1]}for(var i=0;i<data.length;++i){var value=data[i].data[0][1];if(value/total<=options.series.pie.combine.threshold){combined+=value;numCombined++;if(!color){color=data[i].color}}}for(var i=0;i<data.length;++i){var value=data[i].data[0][1];if(numCombined<2||value/total>options.series.pie.combine.threshold){newdata.push($.extend(data[i],{data:[[1,value]],color:data[i].color,label:data[i].label,angle:value*Math.PI*2/total,percent:value/(total/100)}))}}if(numCombined>1){newdata.push({data:[[1,combined]],color:color,label:options.series.pie.combine.label,angle:combined*Math.PI*2/total,percent:combined/(total/100)})}return newdata}function draw(plot,newCtx){if(!target){return}var canvasWidth=plot.getPlaceholder().width(),canvasHeight=plot.getPlaceholder().height(),legendWidth=target.children().filter(".legend").children().width()||0;ctx=newCtx;processed=false;maxRadius=Math.min(canvasWidth,canvasHeight/options.series.pie.tilt)/2;centerTop=canvasHeight/2+options.series.pie.offset.top;centerLeft=canvasWidth/2;if(options.series.pie.offset.left=="auto"){if(options.legend.position.match("w")){centerLeft+=legendWidth/2}else{centerLeft-=legendWidth/2}if(centerLeft<maxRadius){centerLeft=maxRadius}else if(centerLeft>canvasWidth-maxRadius){centerLeft=canvasWidth-maxRadius}}else{centerLeft+=options.series.pie.offset.left}var slices=plot.getData(),attempts=0;do{if(attempts>0){maxRadius*=REDRAW_SHRINK}attempts+=1;clear();if(options.series.pie.tilt<=.8){drawShadow()}}while(!drawPie()&&attempts<REDRAW_ATTEMPTS);if(attempts>=REDRAW_ATTEMPTS){clear();target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>")}if(plot.setSeries&&plot.insertLegend){plot.setSeries(slices);plot.insertLegend()}function clear(){ctx.clearRect(0,0,canvasWidth,canvasHeight);target.children().filter(".pieLabel, .pieLabelBackground").remove()}function drawShadow(){var shadowLeft=options.series.pie.shadow.left;var shadowTop=options.series.pie.shadow.top;var edge=10;var alpha=options.series.pie.shadow.alpha;var radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius;if(radius>=canvasWidth/2-shadowLeft||radius*options.series.pie.tilt>=canvasHeight/2-shadowTop||radius<=edge){return}ctx.save();ctx.translate(shadowLeft,shadowTop);ctx.globalAlpha=alpha;ctx.fillStyle="#000";ctx.translate(centerLeft,centerTop);ctx.scale(1,options.series.pie.tilt);for(var i=1;i<=edge;i++){ctx.beginPath();ctx.arc(0,0,radius,0,Math.PI*2,false);ctx.fill();radius-=i}ctx.restore()}function drawPie(){var startAngle=Math.PI*options.series.pie.startAngle;var radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius;ctx.save();ctx.translate(centerLeft,centerTop);ctx.scale(1,options.series.pie.tilt);ctx.save();var currentAngle=startAngle;for(var i=0;i<slices.length;++i){slices[i].startAngle=currentAngle;drawSlice(slices[i].angle,slices[i].color,true)}ctx.restore();if(options.series.pie.stroke.width>0){ctx.save();ctx.lineWidth=options.series.pie.stroke.width;currentAngle=startAngle;for(var i=0;i<slices.length;++i){drawSlice(slices[i].angle,options.series.pie.stroke.color,false)}ctx.restore()}drawDonutHole(ctx);ctx.restore();if(options.series.pie.label.show){return drawLabels()}else return true;function drawSlice(angle,color,fill){if(angle<=0||isNaN(angle)){return}if(fill){ctx.fillStyle=color}else{ctx.strokeStyle=color;ctx.lineJoin="round"}ctx.beginPath();if(Math.abs(angle-Math.PI*2)>1e-9){ctx.moveTo(0,0)}ctx.arc(0,0,radius,currentAngle,currentAngle+angle/2,false);ctx.arc(0,0,radius,currentAngle+angle/2,currentAngle+angle,false);ctx.closePath();currentAngle+=angle;if(fill){ctx.fill()}else{ctx.stroke()}}function drawLabels(){var currentAngle=startAngle;var radius=options.series.pie.label.radius>1?options.series.pie.label.radius:maxRadius*options.series.pie.label.radius;for(var i=0;i<slices.length;++i){if(slices[i].percent>=options.series.pie.label.threshold*100){if(!drawLabel(slices[i],currentAngle,i)){return false}}currentAngle+=slices[i].angle}return true;function drawLabel(slice,startAngle,index){if(slice.data[0][1]==0){return true}var lf=options.legend.labelFormatter,text,plf=options.series.pie.label.formatter;if(lf){text=lf(slice.label,slice)}else{text=slice.label}if(plf){text=plf(text,slice)}var halfAngle=(startAngle+slice.angle+startAngle)/2;var x=centerLeft+Math.round(Math.cos(halfAngle)*radius);var y=centerTop+Math.round(Math.sin(halfAngle)*radius)*options.series.pie.tilt;var html="<span class='pieLabel' id='pieLabel"+index+"' style='position:absolute;top:"+y+"px;left:"+x+"px;'>"+text+"</span>";target.append(html);var label=target.children("#pieLabel"+index);var labelTop=y-label.height()/2;var labelLeft=x-label.width()/2;label.css("top",labelTop);label.css("left",labelLeft);if(0-labelTop>0||0-labelLeft>0||canvasHeight-(labelTop+label.height())<0||canvasWidth-(labelLeft+label.width())<0){return false}if(options.series.pie.label.background.opacity!=0){var c=options.series.pie.label.background.color;if(c==null){c=slice.color}var pos="top:"+labelTop+"px;left:"+labelLeft+"px;";$("<div class='pieLabelBackground' style='position:absolute;width:"+label.width()+"px;height:"+label.height()+"px;"+pos+"background-color:"+c+";'></div>").css("opacity",options.series.pie.label.background.opacity).insertBefore(label)}return true}}}}function drawDonutHole(layer){if(options.series.pie.innerRadius>0){layer.save();var innerRadius=options.series.pie.innerRadius>1?options.series.pie.innerRadius:maxRadius*options.series.pie.innerRadius;layer.globalCompositeOperation="destination-out";layer.beginPath();layer.fillStyle=options.series.pie.stroke.color;layer.arc(0,0,innerRadius,0,Math.PI*2,false);layer.fill();layer.closePath();layer.restore();layer.save();layer.beginPath();layer.strokeStyle=options.series.pie.stroke.color;layer.arc(0,0,innerRadius,0,Math.PI*2,false);layer.stroke();layer.closePath();layer.restore()}}function isPointInPoly(poly,pt){for(var c=false,i=-1,l=poly.length,j=l-1;++i<l;j=i)(poly[i][1]<=pt[1]&&pt[1]<poly[j][1]||poly[j][1]<=pt[1]&&pt[1]<poly[i][1])&&pt[0]<(poly[j][0]-poly[i][0])*(pt[1]-poly[i][1])/(poly[j][1]-poly[i][1])+poly[i][0]&&(c=!c);return c}function findNearbySlice(mouseX,mouseY){var slices=plot.getData(),options=plot.getOptions(),radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius,x,y;for(var i=0;i<slices.length;++i){var s=slices[i];if(s.pie.show){ctx.save();ctx.beginPath();ctx.moveTo(0,0);ctx.arc(0,0,radius,s.startAngle,s.startAngle+s.angle/2,false);ctx.arc(0,0,radius,s.startAngle+s.angle/2,s.startAngle+s.angle,false);ctx.closePath();x=mouseX-centerLeft;y=mouseY-centerTop;if(ctx.isPointInPath){if(ctx.isPointInPath(mouseX-centerLeft,mouseY-centerTop)){ctx.restore();return{datapoint:[s.percent,s.data],dataIndex:0,series:s,seriesIndex:i}}}else{var p1X=radius*Math.cos(s.startAngle),p1Y=radius*Math.sin(s.startAngle),p2X=radius*Math.cos(s.startAngle+s.angle/4),p2Y=radius*Math.sin(s.startAngle+s.angle/4),p3X=radius*Math.cos(s.startAngle+s.angle/2),p3Y=radius*Math.sin(s.startAngle+s.angle/2),p4X=radius*Math.cos(s.startAngle+s.angle/1.5),p4Y=radius*Math.sin(s.startAngle+s.angle/1.5),p5X=radius*Math.cos(s.startAngle+s.angle),p5Y=radius*Math.sin(s.startAngle+s.angle),arrPoly=[[0,0],[p1X,p1Y],[p2X,p2Y],[p3X,p3Y],[p4X,p4Y],[p5X,p5Y]],arrPoint=[x,y];if(isPointInPoly(arrPoly,arrPoint)){ctx.restore();return{datapoint:[s.percent,s.data],dataIndex:0,series:s,seriesIndex:i}}}ctx.restore()}}return null}function onMouseMove(e){triggerClickHoverEvent("plothover",e)}function onClick(e){triggerClickHoverEvent("plotclick",e)}function triggerClickHoverEvent(eventname,e){var offset=plot.offset();var canvasX=parseInt(e.pageX-offset.left);var canvasY=parseInt(e.pageY-offset.top);var item=findNearbySlice(canvasX,canvasY);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)){unhighlight(h.series)}}}if(item){highlight(item.series,eventname)}var pos={pageX:e.pageX,pageY:e.pageY};target.trigger(eventname,[pos,item])}function highlight(s,auto){var i=indexOfHighlight(s);if(i==-1){highlights.push({series:s,auto:auto});plot.triggerRedrawOverlay()}else if(!auto){highlights[i].auto=false}}function unhighlight(s){if(s==null){highlights=[];plot.triggerRedrawOverlay()}var i=indexOfHighlight(s);if(i!=-1){highlights.splice(i,1);plot.triggerRedrawOverlay()}}function indexOfHighlight(s){for(var i=0;i<highlights.length;++i){var h=highlights[i];if(h.series==s)return i}return-1}function drawOverlay(plot,octx){var options=plot.getOptions();var radius=options.series.pie.radius>1?options.series.pie.radius:maxRadius*options.series.pie.radius;octx.save();octx.translate(centerLeft,centerTop);octx.scale(1,options.series.pie.tilt);for(var i=0;i<highlights.length;++i){drawHighlight(highlights[i].series)}drawDonutHole(octx);octx.restore();function drawHighlight(series){if(series.angle<=0||isNaN(series.angle)){return}octx.fillStyle="rgba(255, 255, 255, "+options.series.pie.highlight.opacity+")";octx.beginPath();if(Math.abs(series.angle-Math.PI*2)>1e-9){octx.moveTo(0,0)}octx.arc(0,0,radius,series.startAngle,series.startAngle+series.angle/2,false);octx.arc(0,0,radius,series.startAngle+series.angle/2,series.startAngle+series.angle,false);octx.closePath();octx.fill()}}}var options={series:{pie:{show:false,radius:"auto",innerRadius:0,startAngle:3/2,tilt:1,shadow:{left:5,top:15,alpha:.02},offset:{top:0,left:"auto"},stroke:{color:"#fff",width:1},label:{show:"auto",formatter:function(label,slice){return"<div style='font-size:x-small;text-align:center;padding:2px;color:"+slice.color+";'>"+label+"<br/>"+Math.round(slice.percent)+"%</div>"},radius:1,background:{color:null,opacity:0},threshold:0},combine:{threshold:-1,color:null,label:"Other"},highlight:{opacity:.5}}}};$.plot.plugins.push({init:init,options:options,name:"pie",version:"1.1"})})(jQuery);
\ No newline at end of file
+!function(e){var i=10,s=.95,t={series:{pie:{show:!1,radius:"auto",innerRadius:0,startAngle:1.5,tilt:1,shadow:{left:5,top:15,alpha:.02},offset:{top:0,left:"auto"},stroke:{color:"#fff",width:1},label:{show:"auto",formatter:function(e,i){return"<div style='font-size:x-small;text-align:center;padding:2px;color:"+i.color+";'>"+e+"<br/>"+Math.round(i.percent)+"%</div>"},radius:1,background:{color:null,opacity:0},threshold:0},combine:{threshold:-1,color:null,label:"Other"},highlight:{opacity:.5}}}};e.plot.plugins.push({init:function(t){function r(i,s,t){y||(y=!0,b=i.getCanvas(),w=e(b).parent(),k=i.getOptions(),i.setData(a(i.getData())))}function a(i){var s,t,r=0,a=0,l=0,n=k.series.pie.combine.color,o=[];for(s=0;s<i.length;++s)t=i[s].data,e.isArray(t)&&1===t.length&&(t=t[0]),e.isArray(t)?!isNaN(parseFloat(t[1]))&&isFinite(t[1])?t[1]=+t[1]:t[1]=0:t=!isNaN(parseFloat(t))&&isFinite(t)?[1,+t]:[1,0],i[s].data=[t];for(s=0;s<i.length;++s)r+=i[s].data[0][1];for(s=0;s<i.length;++s)(t=i[s].data[0][1])/r<=k.series.pie.combine.threshold&&(a+=t,l++,n||(n=i[s].color));for(s=0;s<i.length;++s)t=i[s].data[0][1],(l<2||t/r>k.series.pie.combine.threshold)&&o.push(e.extend(i[s],{data:[[1,t]],color:i[s].color,label:i[s].label,angle:t*Math.PI*2/r,percent:t/(r/100)}));return l>1&&o.push({data:[[1,a]],color:n,label:k.series.pie.combine.label,angle:a*Math.PI*2/r,percent:a/(r/100)}),o}function l(t,r){function a(){m.clearRect(0,0,l,o),w.children().filter(".pieLabel, .pieLabelBackground").remove()}if(w){var l=t.getPlaceholder().width(),o=t.getPlaceholder().height(),p=w.children().filter(".legend").children().width()||0;m=r,y=!1,M=Math.min(l,o/k.series.pie.tilt)/2,A=o/2+k.series.pie.offset.top,P=l/2,"auto"===k.series.pie.offset.left?(k.legend.position.match("w")?P+=p/2:P-=p/2,P<M?P=M:P>l-M&&(P=l-M)):P+=k.series.pie.offset.left;var h=t.getData(),g=0;do{g>0&&(M*=s),g+=1,a(),k.series.pie.tilt<=.8&&function(){var e=k.series.pie.shadow.left,i=k.series.pie.shadow.top,s=k.series.pie.shadow.alpha,t=k.series.pie.radius>1?k.series.pie.radius:M*k.series.pie.radius;if(!(t>=l/2-e||t*k.series.pie.tilt>=o/2-i||t<=10)){m.save(),m.translate(e,i),m.globalAlpha=s,m.fillStyle="#000",m.translate(P,A),m.scale(1,k.series.pie.tilt);for(var r=1;r<=10;r++)m.beginPath(),m.arc(0,0,t,0,2*Math.PI,!1),m.fill(),t-=r;m.restore()}}()}while(!function(){function i(e,i,s){e<=0||isNaN(e)||(s?m.fillStyle=i:(m.strokeStyle=i,m.lineJoin="round"),m.beginPath(),Math.abs(e-2*Math.PI)>1e-9&&m.moveTo(0,0),m.arc(0,0,r,a,a+e/2,!1),m.arc(0,0,r,a+e/2,a+e,!1),m.closePath(),a+=e,s?m.fill():m.stroke())}var s,t=Math.PI*k.series.pie.startAngle,r=k.series.pie.radius>1?k.series.pie.radius:M*k.series.pie.radius;m.save(),m.translate(P,A),m.scale(1,k.series.pie.tilt),m.save();var a=t;for(s=0;s<h.length;++s)h[s].startAngle=a,i(h[s].angle,h[s].color,!0);if(m.restore(),k.series.pie.stroke.width>0){for(m.save(),m.lineWidth=k.series.pie.stroke.width,a=t,s=0;s<h.length;++s)i(h[s].angle,k.series.pie.stroke.color,!1);m.restore()}return n(m),m.restore(),!k.series.pie.label.show||function(){for(var i=t,s=k.series.pie.label.radius>1?k.series.pie.label.radius:M*k.series.pie.label.radius,r=0;r<h.length;++r){if(h[r].percent>=100*k.series.pie.label.threshold&&!function(i,t,r){if(0===i.data[0][1])return!0;var a,n=k.legend.labelFormatter,p=k.series.pie.label.formatter;a=n?n(i.label,i):i.label,p&&(a=p(a,i));var h=(t+i.angle+t)/2,g=P+Math.round(Math.cos(h)*s),c=A+Math.round(Math.sin(h)*s)*k.series.pie.tilt,u="<span class='pieLabel' id='pieLabel"+r+"' style='position:absolute;top:"+c+"px;left:"+g+"px;'>"+a+"</span>";w.append(u);var d=w.children("#pieLabel"+r),f=c-d.height()/2,v=g-d.width()/2;if(d.css("top",f),d.css("left",v),0-f>0||0-v>0||o-(f+d.height())<0||l-(v+d.width())<0)return!1;if(0!==k.series.pie.label.background.opacity){var b=k.series.pie.label.background.color;null==b&&(b=i.color);var M="top:"+f+"px;left:"+v+"px;";e("<div class='pieLabelBackground' style='position:absolute;width:"+d.width()+"px;height:"+d.height()+"px;"+M+"background-color:"+b+";'></div>").css("opacity",k.series.pie.label.background.opacity).insertBefore(d)}return!0}(h[r],i,r))return!1;i+=h[r].angle}return!0}()}()&&g<i);g>=i&&(a(),w.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>")),t.setSeries&&t.insertLegend&&(t.setSeries(h),t.insertLegend())}}function n(e){if(k.series.pie.innerRadius>0){e.save();var i=k.series.pie.innerRadius>1?k.series.pie.innerRadius:M*k.series.pie.innerRadius;e.globalCompositeOperation="destination-out",e.beginPath(),e.fillStyle=k.series.pie.stroke.color,e.arc(0,0,i,0,2*Math.PI,!1),e.fill(),e.closePath(),e.restore(),e.save(),e.beginPath(),e.strokeStyle=k.series.pie.stroke.color,e.arc(0,0,i,0,2*Math.PI,!1),e.stroke(),e.closePath(),e.restore()}}function o(e,i){for(var s=!1,t=-1,r=e.length,a=r-1;++t<r;a=t)(e[t][1]<=i[1]&&i[1]<e[a][1]||e[a][1]<=i[1]&&i[1]<e[t][1])&&i[0]<(e[a][0]-e[t][0])*(i[1]-e[t][1])/(e[a][1]-e[t][1])+e[t][0]&&(s=!s);return s}function p(e,i){for(var s,r,a=t.getData(),l=t.getOptions(),n=l.series.pie.radius>1?l.series.pie.radius:M*l.series.pie.radius,p=0;p<a.length;++p){var h=a[p];if(h.pie.show){if(m.save(),m.beginPath(),m.moveTo(0,0),m.arc(0,0,n,h.startAngle,h.startAngle+h.angle/2,!1),m.arc(0,0,n,h.startAngle+h.angle/2,h.startAngle+h.angle,!1),m.closePath(),s=e-P,r=i-A,m.isPointInPath){if(m.isPointInPath(e-P,i-A))return m.restore(),{datapoint:[h.percent,h.data],dataIndex:0,series:h,seriesIndex:p}}else if(o([[0,0],[n*Math.cos(h.startAngle),n*Math.sin(h.startAngle)],[n*Math.cos(h.startAngle+h.angle/4),n*Math.sin(h.startAngle+h.angle/4)],[n*Math.cos(h.startAngle+h.angle/2),n*Math.sin(h.startAngle+h.angle/2)],[n*Math.cos(h.startAngle+h.angle/1.5),n*Math.sin(h.startAngle+h.angle/1.5)],[n*Math.cos(h.startAngle+h.angle),n*Math.sin(h.startAngle+h.angle)]],[s,r]))return m.restore(),{datapoint:[h.percent,h.data],dataIndex:0,series:h,seriesIndex:p};m.restore()}}return null}function h(e){c("plothover",e)}function g(e){c("plotclick",e)}function c(e,i){var s=t.offset(),r=p(parseInt(i.pageX-s.left),parseInt(i.pageY-s.top));if(k.grid.autoHighlight)for(var a=0;a<I.length;++a){var l=I[a];l.auto!==e||r&&l.series===r.series||d(l.series)}r&&u(r.series,e);var n={pageX:i.pageX,pageY:i.pageY};w.trigger(e,[n,r])}function u(e,i){var s=f(e);-1===s?(I.push({series:e,auto:i}),t.triggerRedrawOverlay()):i||(I[s].auto=!1)}function d(e){null==e&&(I=[],t.triggerRedrawOverlay());var i=f(e);-1!==i&&(I.splice(i,1),t.triggerRedrawOverlay())}function f(e){for(var i=0;i<I.length;++i)if(I[i].series===e)return i;return-1}function v(e,i){var s=e.getOptions(),t=s.series.pie.radius>1?s.series.pie.radius:M*s.series.pie.radius;i.save(),i.translate(P,A),i.scale(1,s.series.pie.tilt);for(var r=0;r<I.length;++r)!function(e){e.angle<=0||isNaN(e.angle)||(i.fillStyle="rgba(255, 255, 255, "+s.series.pie.highlight.opacity+")",i.beginPath(),Math.abs(e.angle-2*Math.PI)>1e-9&&i.moveTo(0,0),i.arc(0,0,t,e.startAngle,e.startAngle+e.angle/2,!1),i.arc(0,0,t,e.startAngle+e.angle/2,e.startAngle+e.angle,!1),i.closePath(),i.fill())}(I[r].series);n(i),i.restore()}var b=null,w=null,k=null,M=null,P=null,A=null,y=!1,m=null,I=[];t.hooks.processOptions.push(function(e,i){i.series.pie.show&&(i.grid.show=!1,"auto"===i.series.pie.label.show&&(i.legend.show?i.series.pie.label.show=!1:i.series.pie.label.show=!0),"auto"===i.series.pie.radius&&(i.series.pie.label.show?i.series.pie.radius=.75:i.series.pie.radius=1),i.series.pie.tilt>1?i.series.pie.tilt=1:i.series.pie.tilt<0&&(i.series.pie.tilt=0))}),t.hooks.bindEvents.push(function(e,i){var s=e.getOptions();s.series.pie.show&&(s.grid.hoverable&&i.unbind("mousemove").mousemove(h),s.grid.clickable&&i.unbind("click").click(g))}),t.hooks.processDatapoints.push(function(e,i,s,t){e.getOptions().series.pie.show&&r(e)}),t.hooks.drawOverlay.push(function(e,i){e.getOptions().series.pie.show&&v(e,i)}),t.hooks.draw.push(function(e,i){e.getOptions().series.pie.show&&l(e,i)})},options:t,name:"pie",version:"1.1"})}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.resize.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.resize.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,3 +1,4 @@
+/* eslint-disable */
 /* Flot plugin for automatically redrawing plots as the placeholder resizes.
 
 Copyright (c) 2007-2014 IOLA and Ole Laursen.
@@ -21,6 +22,7 @@
  */
 (function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);
 
+/* eslint-enable */
 (function ($) {
     var options = { }; // no options
 
@@ -30,14 +32,13 @@
 
             // somebody might have hidden us and we can't plot
             // when we don't have the dimensions
-            if (placeholder.width() == 0 || placeholder.height() == 0)
-                return;
+            if (placeholder.width() === 0 || placeholder.height() === 0) return;
 
             plot.resize();
             plot.setupGrid();
             plot.draw();
         }
-        
+
         function bindEvents(plot, eventHolder) {
             plot.getPlaceholder().resize(onResize);
         }
@@ -45,11 +46,11 @@
         function shutdown(plot, eventHolder) {
             plot.getPlaceholder().unbind("resize", onResize);
         }
-        
+
         plot.hooks.bindEvents.push(bindEvents);
         plot.hooks.shutdown.push(shutdown);
     }
-    
+
     $.plot.plugins.push({
         init: init,
         options: options,
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.resize.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.resize.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,7 +1,1 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
-
-Copyright (c) 2007-2014 IOLA and Ole Laursen.
-Licensed under the MIT license.
-
-*/
-(function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);(function($){var options={};function init(plot){function onResize(){var placeholder=plot.getPlaceholder();if(placeholder.width()==0||placeholder.height()==0)return;plot.resize();plot.setupGrid();plot.draw()}function bindEvents(plot,eventHolder){plot.getPlaceholder().resize(onResize)}function shutdown(plot,eventHolder){plot.getPlaceholder().unbind("resize",onResize)}plot.hooks.bindEvents.push(bindEvents);plot.hooks.shutdown.push(shutdown)}$.plot.plugins.push({init:init,options:options,name:"resize",version:"1.0"})})(jQuery);
\ No newline at end of file
+!function(e,t,i){"$:nomunge";function n(i){!0===s&&(s=i||1);for(var u=r.length-1;u>=0;u--){var c=e(r[u]);if(c[0]==t||c.is(":visible")){var d=c.width(),f=c.height(),g=c.data(m);!g||d===g.w&&f===g.h||(c.trigger(h,[g.w=d,g.h=f]),s=i||!0)}else(g=c.data(m)).w=0,g.h=0}null!==a&&(s&&(null==i||i-s<1e3)?a=t.requestAnimationFrame(n):(a=setTimeout(n,o[l]),s=!1))}var a,r=[],o=e.resize=e.extend(e.resize,{}),s=!1,u="setTimeout",h="resize",m=h+"-special-event",l="pendingDelay",c="activeDelay",d="throttleWindow";o[l]=200,o[c]=20,o[d]=!0,e.event.special[h]={setup:function(){if(!o[d]&&this[u])return!1;var t=e(this);r.push(this),t.data(m,{w:t.width(),h:t.height()}),1===r.length&&(a=i,n())},teardown:function(){if(!o[d]&&this[u])return!1;for(var t=e(this),i=r.length-1;i>=0;i--)if(r[i]==this){r.splice(i,1);break}t.removeData(m),r.length||(s?cancelAnimationFrame(a):clearTimeout(a),a=null)},add:function(t){function n(t,n,r){var o=e(this),s=o.data(m)||{};s.w=n!==i?n:o.width(),s.h=r!==i?r:o.height(),a.apply(this,arguments)}if(!o[d]&&this[u])return!1;var a;if(e.isFunction(t))return a=t,n;a=t.handler,t.handler=n}},t.requestAnimationFrame||(t.requestAnimationFrame=t.webkitRequestAnimationFrame||t.mozRequestAnimationFrame||t.oRequestAnimationFrame||t.msRequestAnimationFrame||function(e,i){return t.setTimeout(function(){e((new Date).getTime())},o[c])}),t.cancelAnimationFrame||(t.cancelAnimationFrame=t.webkitCancelRequestAnimationFrame||t.mozCancelRequestAnimationFrame||t.oCancelRequestAnimationFrame||t.msCancelRequestAnimationFrame||clearTimeout)}(jQuery,this),function(e){jQuery.plot.plugins.push({init:function(e){function t(){var t=e.getPlaceholder();0!==t.width()&&0!==t.height()&&(e.resize(),e.setupGrid(),e.draw())}e.hooks.bindEvents.push(function(e,i){e.getPlaceholder().resize(t)}),e.hooks.shutdown.push(function(e,i){e.getPlaceholder().unbind("resize",t)})},options:{},name:"resize",version:"1.0"})}();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.saturated.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,43 @@
+(function ($) {
+    'use strict';
+    var saturated = {
+        saturate: function (a) {
+            if (a === Infinity) {
+                return Number.MAX_VALUE;
+            }
+
+            if (a === -Infinity) {
+                return -Number.MAX_VALUE;
+            }
+
+            return a;
+        },
+        delta: function(min, max, noTicks) {
+            return ((max - min) / noTicks) === Infinity ? (max / noTicks - min / noTicks) : (max - min) / noTicks
+        },
+        multiply: function (a, b) {
+            return saturated.saturate(a * b);
+        },
+        // returns c * bInt * a. Beahves properly in the case where c is negative
+        // and bInt * a is bigger that Number.MAX_VALUE (Infinity)
+        multiplyAdd: function (a, bInt, c) {
+            if (isFinite(a * bInt)) {
+                return saturated.saturate(a * bInt + c);
+            } else {
+                var result = c;
+
+                for (var i = 0; i < bInt; i++) {
+                    result += a;
+                }
+
+                return saturated.saturate(result);
+            }
+        },
+        // round to nearby lower multiple of base
+        floorInBase: function(n, base) {
+            return base * Math.floor(n / base);
+        }
+    };
+
+    $.plot.saturated = saturated;
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.saturated.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(t){"use strict";var r={saturate:function(t){return t===1/0?Number.MAX_VALUE:t===-1/0?-Number.MAX_VALUE:t},delta:function(t,r,u){return(r-t)/u==1/0?r/u-t/u:(r-t)/u},multiply:function(t,u){return r.saturate(t*u)},multiplyAdd:function(t,u,n){if(isFinite(t*u))return r.saturate(t*u+n);for(var e=n,a=0;a<u;a++)e+=t;return r.saturate(e)},floorInBase:function(t,r){return r*Math.floor(t/r)}};jQuery.plot.saturated=r}();
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.selection.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.selection.js	Wed Oct 16 13:00:43 2019 +0200
@@ -6,10 +6,11 @@
 The plugin supports these options:
 
 selection: {
-	mode: null or "x" or "y" or "xy",
-	color: color,
-	shape: "round" or "miter" or "bevel",
-	minSize: number of pixels
+    mode: null or "x" or "y" or "xy" or "smart",
+    color: color,
+    shape: "round" or "miter" or "bevel",
+    visualization: "fill" or "focus",
+    minSize: number of pixels
 }
 
 Selection support is enabled by setting the mode to one of "x", "y" or "xy".
@@ -19,6 +20,12 @@
 later on, you can get to it with plot.getOptions().selection.color). "shape"
 is the shape of the corners of the selection.
 
+The way how the selection is visualized, can be changed by using the option
+"visualization". Flot currently supports two modes: "focus" and "fill". The
+option "focus" draws a colored bezel around the selected area while keeping
+the selected area clear. The option "fill" highlights (i.e., fills) the
+selected area with a colored highlight.
+
 "minSize" is the minimum size a selection can be in pixels. This value can
 be customized to determine the smallest size a selection can be and still
 have the selection rectangle be displayed. When customizing this value, the
@@ -33,11 +40,11 @@
 the DOM element you passed into the plot function. The event handler gets a
 parameter with the ranges selected on the axes, like this:
 
-	placeholder.bind( "plotselected", function( event, ranges ) {
-		alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
-		// similar for yaxis - with multiple axes, the extra ones are in
-		// x2axis, x3axis, ...
-	});
+    placeholder.bind( "plotselected", function( event, ranges ) {
+        alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
+        // similar for yaxis - with multiple axes, the extra ones are in
+        // x2axis, x3axis, ...
+    });
 
 The "plotselected" event is only fired when the user has finished making the
 selection. A "plotselecting" event is fired during the process with the same
@@ -58,7 +65,7 @@
   an yaxis range and both xaxis and yaxis if the selection mode is "xy", like
   this:
 
-	setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
+    setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
 
   setSelection will trigger the "plotselected" event when called. If you don't
   want that to happen, e.g. if you're inside a "plotselected" handler, pass
@@ -81,10 +88,14 @@
 (function ($) {
     function init(plot) {
         var selection = {
-                first: { x: -1, y: -1}, second: { x: -1, y: -1},
-                show: false,
-                active: false
-            };
+            first: {x: -1, y: -1},
+            second: {x: -1, y: -1},
+            show: false,
+            currentMode: 'xy',
+            active: false
+        };
+
+        var SNAPPING_CONSTANT = $.plot.uiConstants.SNAPPING_CONSTANT;
 
         // FIXME: The drag handling implemented here should be
         // abstracted out, there's some similar code from a library in
@@ -94,19 +105,23 @@
         var savedhandlers = {};
 
         var mouseUpHandler = null;
-        
+
         function onMouseMove(e) {
             if (selection.active) {
                 updateSelection(e);
-                
+
                 plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
             }
         }
 
         function onMouseDown(e) {
-            if (e.which != 1)  // only accept left-click
-                return;
-            
+            var o = plot.getOptions();
+            // only accept left-click
+            if (e.which !== 1 || o.selection.mode === null) return;
+
+            // reinitialize currentMode
+            selection.currentMode = 'xy';
+
             // cancel out any text selections
             document.body.focus();
 
@@ -127,26 +142,29 @@
             // this is a bit silly, but we have to use a closure to be
             // able to whack the same handler again
             mouseUpHandler = function (e) { onMouseUp(e); };
-            
+
             $(document).one("mouseup", mouseUpHandler);
         }
 
         function onMouseUp(e) {
             mouseUpHandler = null;
-            
+
             // revert drag stuff for old-school browsers
-            if (document.onselectstart !== undefined)
+            if (document.onselectstart !== undefined) {
                 document.onselectstart = savedhandlers.onselectstart;
-            if (document.ondrag !== undefined)
+            }
+
+            if (document.ondrag !== undefined) {
                 document.ondrag = savedhandlers.ondrag;
+            }
 
             // no more dragging
             selection.active = false;
             updateSelection(e);
 
-            if (selectionIsSane())
+            if (selectionIsSane()) {
                 triggerSelectedEvent();
-            else {
+            } else {
                 // this counts as a clear
                 plot.getPlaceholder().trigger("plotunselected", [ ]);
                 plot.getPlaceholder().trigger("plotselecting", [ null ]);
@@ -156,15 +174,27 @@
         }
 
         function getSelection() {
-            if (!selectionIsSane())
-                return null;
-            
+            if (!selectionIsSane()) return null;
+
             if (!selection.show) return null;
 
-            var r = {}, c1 = selection.first, c2 = selection.second;
+            var r = {},
+                c1 = {x: selection.first.x, y: selection.first.y},
+                c2 = {x: selection.second.x, y: selection.second.y};
+
+            if (selectionDirection(plot) === 'x') {
+                c1.y = 0;
+                c2.y = plot.height();
+            }
+
+            if (selectionDirection(plot) === 'y') {
+                c1.x = 0;
+                c2.x = plot.width();
+            }
+
             $.each(plot.getAxes(), function (name, axis) {
                 if (axis.used) {
-                    var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]); 
+                    var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
                     r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
                 }
             });
@@ -177,47 +207,77 @@
             plot.getPlaceholder().trigger("plotselected", [ r ]);
 
             // backwards-compat stuff, to be removed in future
-            if (r.xaxis && r.yaxis)
+            if (r.xaxis && r.yaxis) {
                 plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
+            }
         }
 
         function clamp(min, value, max) {
-            return value < min ? min: (value > max ? max: value);
+            return value < min ? min : (value > max ? max : value);
+        }
+
+        function selectionDirection(plot) {
+            var o = plot.getOptions();
+
+            if (o.selection.mode === 'smart') {
+                return selection.currentMode;
+            } else {
+                return o.selection.mode;
+            }
+        }
+
+        function updateMode(pos) {
+            if (selection.first) {
+                var delta = {
+                    x: pos.x - selection.first.x,
+                    y: pos.y - selection.first.y
+                };
+
+                if (Math.abs(delta.x) < SNAPPING_CONSTANT) {
+                    selection.currentMode = 'y';
+                } else if (Math.abs(delta.y) < SNAPPING_CONSTANT) {
+                    selection.currentMode = 'x';
+                } else {
+                    selection.currentMode = 'xy';
+                }
+            }
         }
 
         function setSelectionPos(pos, e) {
-            var o = plot.getOptions();
             var offset = plot.getPlaceholder().offset();
             var plotOffset = plot.getPlotOffset();
             pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
             pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
 
-            if (o.selection.mode == "y")
-                pos.x = pos == selection.first ? 0 : plot.width();
+            if (pos !== selection.first) updateMode(pos);
 
-            if (o.selection.mode == "x")
-                pos.y = pos == selection.first ? 0 : plot.height();
+            if (selectionDirection(plot) === "y") {
+                pos.x = pos === selection.first ? 0 : plot.width();
+            }
+
+            if (selectionDirection(plot) === "x") {
+                pos.y = pos === selection.first ? 0 : plot.height();
+            }
         }
 
         function updateSelection(pos) {
-            if (pos.pageX == null)
-                return;
+            if (pos.pageX == null) return;
 
             setSelectionPos(selection.second, pos);
             if (selectionIsSane()) {
                 selection.show = true;
                 plot.triggerRedrawOverlay();
-            }
-            else
-                clearSelection(true);
+            } else clearSelection(true);
         }
 
         function clearSelection(preventEvent) {
             if (selection.show) {
                 selection.show = false;
+                selection.currentMode = '';
                 plot.triggerRedrawOverlay();
-                if (!preventEvent)
+                if (!preventEvent) {
                     plot.getPlaceholder().trigger("plotunselected", [ ]);
+                }
             }
         }
 
@@ -227,10 +287,13 @@
 
             for (var k in axes) {
                 axis = axes[k];
-                if (axis.direction == coord) {
+                if (axis.direction === coord) {
                     key = coord + axis.n + "axis";
-                    if (!ranges[key] && axis.n == 1)
-                        key = coord + "axis"; // support x1axis as xaxis
+                    if (!ranges[key] && axis.n === 1) {
+                        // support x1axis as xaxis
+                        key = coord + "axis";
+                    }
+
                     if (ranges[key]) {
                         from = ranges[key].from;
                         to = ranges[key].to;
@@ -241,7 +304,7 @@
 
             // backwards-compat stuff - to be removed in future
             if (!ranges[key]) {
-                axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
+                axis = coord === "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
                 from = ranges[coord + "1"];
                 to = ranges[coord + "2"];
             }
@@ -252,39 +315,36 @@
                 from = to;
                 to = tmp;
             }
-            
+
             return { from: from, to: to, axis: axis };
         }
-        
+
         function setSelection(ranges, preventEvent) {
-            var axis, range, o = plot.getOptions();
+            var range;
 
-            if (o.selection.mode == "y") {
+            if (selectionDirection(plot) === "y") {
                 selection.first.x = 0;
                 selection.second.x = plot.width();
-            }
-            else {
+            } else {
                 range = extractRange(ranges, "x");
-
                 selection.first.x = range.axis.p2c(range.from);
                 selection.second.x = range.axis.p2c(range.to);
             }
 
-            if (o.selection.mode == "x") {
+            if (selectionDirection(plot) === "x") {
                 selection.first.y = 0;
                 selection.second.y = plot.height();
-            }
-            else {
+            } else {
                 range = extractRange(ranges, "y");
-
                 selection.first.y = range.axis.p2c(range.from);
                 selection.second.y = range.axis.p2c(range.to);
             }
 
             selection.show = true;
             plot.triggerRedrawOverlay();
-            if (!preventEvent && selectionIsSane())
+            if (!preventEvent && selectionIsSane()) {
                 triggerSelectedEvent();
+            }
         }
 
         function selectionIsSane() {
@@ -305,6 +365,88 @@
             }
         });
 
+        function drawSelectionDecorations(ctx, x, y, w, h, oX, oY, mode) {
+            var spacing = 3;
+            var fullEarWidth = 15;
+            var earWidth = Math.max(0, Math.min(fullEarWidth, w / 2 - 2, h / 2 - 2));
+            ctx.fillStyle = '#ffffff';
+
+            if (mode === 'xy') {
+                ctx.beginPath();
+                ctx.moveTo(x, y + earWidth);
+                ctx.lineTo(x - 3, y + earWidth);
+                ctx.lineTo(x - 3, y - 3);
+                ctx.lineTo(x + earWidth, y - 3);
+                ctx.lineTo(x + earWidth, y);
+                ctx.lineTo(x, y);
+                ctx.closePath();
+
+                ctx.moveTo(x, y + h - earWidth);
+                ctx.lineTo(x - 3, y + h - earWidth);
+                ctx.lineTo(x - 3, y + h + 3);
+                ctx.lineTo(x + earWidth, y + h + 3);
+                ctx.lineTo(x + earWidth, y + h);
+                ctx.lineTo(x, y + h);
+                ctx.closePath();
+
+                ctx.moveTo(x + w, y + earWidth);
+                ctx.lineTo(x + w + 3, y + earWidth);
+                ctx.lineTo(x + w + 3, y - 3);
+                ctx.lineTo(x + w - earWidth, y - 3);
+                ctx.lineTo(x + w - earWidth, y);
+                ctx.lineTo(x + w, y);
+                ctx.closePath();
+
+                ctx.moveTo(x + w, y + h - earWidth);
+                ctx.lineTo(x + w + 3, y + h - earWidth);
+                ctx.lineTo(x + w + 3, y + h + 3);
+                ctx.lineTo(x + w - earWidth, y + h + 3);
+                ctx.lineTo(x + w - earWidth, y + h);
+                ctx.lineTo(x + w, y + h);
+                ctx.closePath();
+
+                ctx.stroke();
+                ctx.fill();
+            }
+
+            x = oX;
+            y = oY;
+
+            if (mode === 'x') {
+                ctx.beginPath();
+                ctx.moveTo(x, y + fullEarWidth);
+                ctx.lineTo(x, y - fullEarWidth);
+                ctx.lineTo(x - spacing, y - fullEarWidth);
+                ctx.lineTo(x - spacing, y + fullEarWidth);
+                ctx.closePath();
+
+                ctx.moveTo(x + w, y + fullEarWidth);
+                ctx.lineTo(x + w, y - fullEarWidth);
+                ctx.lineTo(x + w + spacing, y - fullEarWidth);
+                ctx.lineTo(x + w + spacing, y + fullEarWidth);
+                ctx.closePath();
+                ctx.stroke();
+                ctx.fill();
+            }
+
+            if (mode === 'y') {
+                ctx.beginPath();
+
+                ctx.moveTo(x - fullEarWidth, y);
+                ctx.lineTo(x + fullEarWidth, y);
+                ctx.lineTo(x + fullEarWidth, y - spacing);
+                ctx.lineTo(x - fullEarWidth, y - spacing);
+                ctx.closePath();
+
+                ctx.moveTo(x - fullEarWidth, y + h);
+                ctx.lineTo(x + fullEarWidth, y + h);
+                ctx.lineTo(x + fullEarWidth, y + h + spacing);
+                ctx.lineTo(x - fullEarWidth, y + h + spacing);
+                ctx.closePath();
+                ctx.stroke();
+                ctx.fill();
+            }
+        }
 
         plot.hooks.drawOverlay.push(function (plot, ctx) {
             // draw selection
@@ -316,32 +458,58 @@
                 ctx.translate(plotOffset.left, plotOffset.top);
 
                 var c = $.color.parse(o.selection.color);
+                var visualization = o.selection.visualization;
 
-                ctx.strokeStyle = c.scale('a', 0.8).toString();
+                var scalingFactor = 1;
+
+                // use a dimmer scaling factor if visualization is "fill"
+                if (visualization === "fill") {
+                    scalingFactor = 0.8;
+                }
+
+                ctx.strokeStyle = c.scale('a', scalingFactor).toString();
                 ctx.lineWidth = 1;
                 ctx.lineJoin = o.selection.shape;
                 ctx.fillStyle = c.scale('a', 0.4).toString();
 
                 var x = Math.min(selection.first.x, selection.second.x) + 0.5,
+                    oX = x,
                     y = Math.min(selection.first.y, selection.second.y) + 0.5,
+                    oY = y,
                     w = Math.abs(selection.second.x - selection.first.x) - 1,
                     h = Math.abs(selection.second.y - selection.first.y) - 1;
 
-                ctx.fillRect(x, y, w, h);
-                ctx.strokeRect(x, y, w, h);
+                if (selectionDirection(plot) === 'x') {
+                    h += y;
+                    y = 0;
+                }
+
+                if (selectionDirection(plot) === 'y') {
+                    w += x;
+                    x = 0;
+                }
+
+                if (visualization === "fill") {
+                    ctx.fillRect(x, y, w, h);
+                    ctx.strokeRect(x, y, w, h);
+                } else {
+                    ctx.fillRect(0, 0, plot.width(), plot.height());
+                    ctx.clearRect(x, y, w, h);
+                    drawSelectionDecorations(ctx, x, y, w, h, oX, oY, selectionDirection(plot));
+                }
 
                 ctx.restore();
             }
         });
-        
+
         plot.hooks.shutdown.push(function (plot, eventHolder) {
             eventHolder.unbind("mousemove", onMouseMove);
             eventHolder.unbind("mousedown", onMouseDown);
-            
-            if (mouseUpHandler)
+
+            if (mouseUpHandler) {
                 $(document).unbind("mouseup", mouseUpHandler);
+            }
         });
-
     }
 
     $.plot.plugins.push({
@@ -349,7 +517,8 @@
         options: {
             selection: {
                 mode: null, // one of null, "x", "y" or "xy"
-                color: "#e8cfac",
+                visualization: "focus", // "focus" or "fill"
+                color: "#888888",
                 shape: "round", // one of "round", "miter", or "bevel"
                 minSize: 5 // minimum number of pixels
             }
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.selection.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.selection.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,7 +1,1 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
-
-Copyright (c) 2007-2014 IOLA and Ole Laursen.
-Licensed under the MIT license.
-
-*/
-(function($){function init(plot){var selection={first:{x:-1,y:-1},second:{x:-1,y:-1},show:false,active:false};var savedhandlers={};var mouseUpHandler=null;function onMouseMove(e){if(selection.active){updateSelection(e);plot.getPlaceholder().trigger("plotselecting",[getSelection()])}}function onMouseDown(e){if(e.which!=1)return;document.body.focus();if(document.onselectstart!==undefined&&savedhandlers.onselectstart==null){savedhandlers.onselectstart=document.onselectstart;document.onselectstart=function(){return false}}if(document.ondrag!==undefined&&savedhandlers.ondrag==null){savedhandlers.ondrag=document.ondrag;document.ondrag=function(){return false}}setSelectionPos(selection.first,e);selection.active=true;mouseUpHandler=function(e){onMouseUp(e)};$(document).one("mouseup",mouseUpHandler)}function onMouseUp(e){mouseUpHandler=null;if(document.onselectstart!==undefined)document.onselectstart=savedhandlers.onselectstart;if(document.ondrag!==undefined)document.ondrag=savedhandlers.ondrag;selection.active=false;updateSelection(e);if(selectionIsSane())triggerSelectedEvent();else{plot.getPlaceholder().trigger("plotunselected",[]);plot.getPlaceholder().trigger("plotselecting",[null])}return false}function getSelection(){if(!selectionIsSane())return null;if(!selection.show)return null;var r={},c1=selection.first,c2=selection.second;$.each(plot.getAxes(),function(name,axis){if(axis.used){var p1=axis.c2p(c1[axis.direction]),p2=axis.c2p(c2[axis.direction]);r[name]={from:Math.min(p1,p2),to:Math.max(p1,p2)}}});return r}function triggerSelectedEvent(){var r=getSelection();plot.getPlaceholder().trigger("plotselected",[r]);if(r.xaxis&&r.yaxis)plot.getPlaceholder().trigger("selected",[{x1:r.xaxis.from,y1:r.yaxis.from,x2:r.xaxis.to,y2:r.yaxis.to}])}function clamp(min,value,max){return value<min?min:value>max?max:value}function setSelectionPos(pos,e){var o=plot.getOptions();var offset=plot.getPlaceholder().offset();var plotOffset=plot.getPlotOffset();pos.x=clamp(0,e.pageX-offset.left-plotOffset.left,plot.width());pos.y=clamp(0,e.pageY-offset.top-plotOffset.top,plot.height());if(o.selection.mode=="y")pos.x=pos==selection.first?0:plot.width();if(o.selection.mode=="x")pos.y=pos==selection.first?0:plot.height()}function updateSelection(pos){if(pos.pageX==null)return;setSelectionPos(selection.second,pos);if(selectionIsSane()){selection.show=true;plot.triggerRedrawOverlay()}else clearSelection(true)}function clearSelection(preventEvent){if(selection.show){selection.show=false;plot.triggerRedrawOverlay();if(!preventEvent)plot.getPlaceholder().trigger("plotunselected",[])}}function extractRange(ranges,coord){var axis,from,to,key,axes=plot.getAxes();for(var k in axes){axis=axes[k];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"?plot.getXAxes()[0]:plot.getYAxes()[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 setSelection(ranges,preventEvent){var axis,range,o=plot.getOptions();if(o.selection.mode=="y"){selection.first.x=0;selection.second.x=plot.width()}else{range=extractRange(ranges,"x");selection.first.x=range.axis.p2c(range.from);selection.second.x=range.axis.p2c(range.to)}if(o.selection.mode=="x"){selection.first.y=0;selection.second.y=plot.height()}else{range=extractRange(ranges,"y");selection.first.y=range.axis.p2c(range.from);selection.second.y=range.axis.p2c(range.to)}selection.show=true;plot.triggerRedrawOverlay();if(!preventEvent&&selectionIsSane())triggerSelectedEvent()}function selectionIsSane(){var minSize=plot.getOptions().selection.minSize;return Math.abs(selection.second.x-selection.first.x)>=minSize&&Math.abs(selection.second.y-selection.first.y)>=minSize}plot.clearSelection=clearSelection;plot.setSelection=setSelection;plot.getSelection=getSelection;plot.hooks.bindEvents.push(function(plot,eventHolder){var o=plot.getOptions();if(o.selection.mode!=null){eventHolder.mousemove(onMouseMove);eventHolder.mousedown(onMouseDown)}});plot.hooks.drawOverlay.push(function(plot,ctx){if(selection.show&&selectionIsSane()){var plotOffset=plot.getPlotOffset();var o=plot.getOptions();ctx.save();ctx.translate(plotOffset.left,plotOffset.top);var c=$.color.parse(o.selection.color);ctx.strokeStyle=c.scale("a",.8).toString();ctx.lineWidth=1;ctx.lineJoin=o.selection.shape;ctx.fillStyle=c.scale("a",.4).toString();var x=Math.min(selection.first.x,selection.second.x)+.5,y=Math.min(selection.first.y,selection.second.y)+.5,w=Math.abs(selection.second.x-selection.first.x)-1,h=Math.abs(selection.second.y-selection.first.y)-1;ctx.fillRect(x,y,w,h);ctx.strokeRect(x,y,w,h);ctx.restore()}});plot.hooks.shutdown.push(function(plot,eventHolder){eventHolder.unbind("mousemove",onMouseMove);eventHolder.unbind("mousedown",onMouseDown);if(mouseUpHandler)$(document).unbind("mouseup",mouseUpHandler)})}$.plot.plugins.push({init:init,options:{selection:{mode:null,color:"#e8cfac",shape:"round",minSize:5}},name:"selection",version:"1.1"})})(jQuery);
\ No newline at end of file
+!function(e){e.plot.plugins.push({init:function(o){function t(e){m.active&&(d(e),o.getPlaceholder().trigger("plotselecting",[l()]))}function n(t){var n=o.getOptions();1===t.which&&null!==n.selection.mode&&(m.currentMode="xy",document.body.focus(),void 0!==document.onselectstart&&null==v.onselectstart&&(v.onselectstart=document.onselectstart,document.onselectstart=function(){return!1}),void 0!==document.ondrag&&null==v.ondrag&&(v.ondrag=document.ondrag,document.ondrag=function(){return!1}),u(m.first,t),m.active=!0,T=function(e){i(e)},e(document).one("mouseup",T))}function i(e){return T=null,void 0!==document.onselectstart&&(document.onselectstart=v.onselectstart),void 0!==document.ondrag&&(document.ondrag=v.ondrag),m.active=!1,d(e),g()?r():(o.getPlaceholder().trigger("plotunselected",[]),o.getPlaceholder().trigger("plotselecting",[null])),!1}function l(){if(!g())return null;if(!m.show)return null;var t={},n={x:m.first.x,y:m.first.y},i={x:m.second.x,y:m.second.y};return"x"===c(o)&&(n.y=0,i.y=o.height()),"y"===c(o)&&(n.x=0,i.x=o.width()),e.each(o.getAxes(),function(e,o){if(o.used){var l=o.c2p(n[o.direction]),r=o.c2p(i[o.direction]);t[e]={from:Math.min(l,r),to:Math.max(l,r)}}}),t}function r(){var e=l();o.getPlaceholder().trigger("plotselected",[e]),e.xaxis&&e.yaxis&&o.getPlaceholder().trigger("selected",[{x1:e.xaxis.from,y1:e.yaxis.from,x2:e.xaxis.to,y2:e.yaxis.to}])}function s(e,o,t){return o<e?e:o>t?t:o}function c(e){var o=e.getOptions();return"smart"===o.selection.mode?m.currentMode:o.selection.mode}function a(e){if(m.first){var o={x:e.x-m.first.x,y:e.y-m.first.y};Math.abs(o.x)<y?m.currentMode="y":Math.abs(o.y)<y?m.currentMode="x":m.currentMode="xy"}}function u(e,t){var n=o.getPlaceholder().offset(),i=o.getPlotOffset();e.x=s(0,t.pageX-n.left-i.left,o.width()),e.y=s(0,t.pageY-n.top-i.top,o.height()),e!==m.first&&a(e),"y"===c(o)&&(e.x=e===m.first?0:o.width()),"x"===c(o)&&(e.y=e===m.first?0:o.height())}function d(e){null!=e.pageX&&(u(m.second,e),g()?(m.show=!0,o.triggerRedrawOverlay()):f(!0))}function f(e){m.show&&(m.show=!1,m.currentMode="",o.triggerRedrawOverlay(),e||o.getPlaceholder().trigger("plotunselected",[]))}function h(e,t){var n,i,l,r,s=o.getAxes();for(var c in s)if((n=s[c]).direction===t&&(r=t+n.n+"axis",e[r]||1!==n.n||(r=t+"axis"),e[r])){i=e[r].from,l=e[r].to;break}if(e[r]||(n="x"===t?o.getXAxes()[0]:o.getYAxes()[0],i=e[t+"1"],l=e[t+"2"]),null!=i&&null!=l&&i>l){var a=i;i=l,l=a}return{from:i,to:l,axis:n}}function g(){var e=o.getOptions().selection.minSize;return Math.abs(m.second.x-m.first.x)>=e&&Math.abs(m.second.y-m.first.y)>=e}function x(e,o,t,n,i,l,r,s){var c=Math.max(0,Math.min(15,n/2-2,i/2-2));e.fillStyle="#ffffff","xy"===s&&(e.beginPath(),e.moveTo(o,t+c),e.lineTo(o-3,t+c),e.lineTo(o-3,t-3),e.lineTo(o+c,t-3),e.lineTo(o+c,t),e.lineTo(o,t),e.closePath(),e.moveTo(o,t+i-c),e.lineTo(o-3,t+i-c),e.lineTo(o-3,t+i+3),e.lineTo(o+c,t+i+3),e.lineTo(o+c,t+i),e.lineTo(o,t+i),e.closePath(),e.moveTo(o+n,t+c),e.lineTo(o+n+3,t+c),e.lineTo(o+n+3,t-3),e.lineTo(o+n-c,t-3),e.lineTo(o+n-c,t),e.lineTo(o+n,t),e.closePath(),e.moveTo(o+n,t+i-c),e.lineTo(o+n+3,t+i-c),e.lineTo(o+n+3,t+i+3),e.lineTo(o+n-c,t+i+3),e.lineTo(o+n-c,t+i),e.lineTo(o+n,t+i),e.closePath(),e.stroke(),e.fill()),o=l,t=r,"x"===s&&(e.beginPath(),e.moveTo(o,t+15),e.lineTo(o,t-15),e.lineTo(o-3,t-15),e.lineTo(o-3,t+15),e.closePath(),e.moveTo(o+n,t+15),e.lineTo(o+n,t-15),e.lineTo(o+n+3,t-15),e.lineTo(o+n+3,t+15),e.closePath(),e.stroke(),e.fill()),"y"===s&&(e.beginPath(),e.moveTo(o-15,t),e.lineTo(o+15,t),e.lineTo(o+15,t-3),e.lineTo(o-15,t-3),e.closePath(),e.moveTo(o-15,t+i),e.lineTo(o+15,t+i),e.lineTo(o+15,t+i+3),e.lineTo(o-15,t+i+3),e.closePath(),e.stroke(),e.fill())}var m={first:{x:-1,y:-1},second:{x:-1,y:-1},show:!1,currentMode:"xy",active:!1},y=e.plot.uiConstants.SNAPPING_CONSTANT,v={},T=null;o.clearSelection=f,o.setSelection=function(e,t){var n;"y"===c(o)?(m.first.x=0,m.second.x=o.width()):(n=h(e,"x"),m.first.x=n.axis.p2c(n.from),m.second.x=n.axis.p2c(n.to)),"x"===c(o)?(m.first.y=0,m.second.y=o.height()):(n=h(e,"y"),m.first.y=n.axis.p2c(n.from),m.second.y=n.axis.p2c(n.to)),m.show=!0,o.triggerRedrawOverlay(),!t&&g()&&r()},o.getSelection=l,o.hooks.bindEvents.push(function(e,o){null!=e.getOptions().selection.mode&&(o.mousemove(t),o.mousedown(n))}),o.hooks.drawOverlay.push(function(o,t){if(m.show&&g()){var n=o.getPlotOffset(),i=o.getOptions();t.save(),t.translate(n.left,n.top);var l=e.color.parse(i.selection.color),r=i.selection.visualization,s=1;"fill"===r&&(s=.8),t.strokeStyle=l.scale("a",s).toString(),t.lineWidth=1,t.lineJoin=i.selection.shape,t.fillStyle=l.scale("a",.4).toString();var a=Math.min(m.first.x,m.second.x)+.5,u=a,d=Math.min(m.first.y,m.second.y)+.5,f=d,h=Math.abs(m.second.x-m.first.x)-1,y=Math.abs(m.second.y-m.first.y)-1;"x"===c(o)&&(y+=d,d=0),"y"===c(o)&&(h+=a,a=0),"fill"===r?(t.fillRect(a,d,h,y),t.strokeRect(a,d,h,y)):(t.fillRect(0,0,o.width(),o.height()),t.clearRect(a,d,h,y),x(t,a,d,h,y,u,f,c(o))),t.restore()}}),o.hooks.shutdown.push(function(o,i){i.unbind("mousemove",t),i.unbind("mousedown",n),T&&e(document).unbind("mouseup",T)})},options:{selection:{mode:null,visualization:"focus",color:"#888888",shape:"round",minSize:5}},name:"selection",version:"1.1"})}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.stack.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.stack.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,4 +1,4 @@
-/* Flot plugin for stacking data sets rather than overlyaing them.
+/* Flot plugin for stacking data sets rather than overlaying them.
 
 Copyright (c) 2007-2014 IOLA and Ole Laursen.
 Licensed under the MIT license.
@@ -14,16 +14,16 @@
 key (which can be any number or string or just "true"). To specify the default
 stack, you can set the stack option like this:
 
-	series: {
-		stack: null/false, true, or a key (number/string)
-	}
+    series: {
+        stack: null/false, true, or a key (number/string)
+    }
 
 You can also specify it for a single series, like this:
 
-	$.plot( $("#placeholder"), [{
-		data: [ ... ],
-		stack: true
-	}])
+    $.plot( $("#placeholder"), [{
+        data: [ ... ],
+        stack: true
+    }])
 
 The stacking order is determined by the order of the data series in the array
 (later series end up on top of the previous).
@@ -39,28 +39,53 @@
     var options = {
         series: { stack: null } // or number/string
     };
-    
+
     function init(plot) {
         function findMatchingSeries(s, allseries) {
             var res = null;
             for (var i = 0; i < allseries.length; ++i) {
-                if (s == allseries[i])
-                    break;
-                
-                if (allseries[i].stack == s.stack)
+                if (s === allseries[i]) break;
+
+                if (allseries[i].stack === s.stack) {
                     res = allseries[i];
+                }
             }
-            
+
             return res;
         }
-        
+
+        function addBottomPoints (s, datapoints) {
+            var formattedPoints = [];
+            for (var i = 0; i < datapoints.points.length; i += 2) {
+                formattedPoints.push(datapoints.points[i]);
+                formattedPoints.push(datapoints.points[i + 1]);
+                formattedPoints.push(0);
+            }
+
+            datapoints.format.push({
+                x: false,
+                y: true,
+                number: true,
+                required: false,
+                computeRange: s.yaxis.options.autoScale !== 'none',
+                defaultValue: 0
+            });
+            datapoints.points = formattedPoints;
+            datapoints.pointsize = 3;
+        }
+
         function stackData(plot, s, datapoints) {
-            if (s.stack == null || s.stack === false)
-                return;
+            if (s.stack == null || s.stack === false) return;
+
+            var needsBottom = s.bars.show || (s.lines.show && s.lines.fill);
+            var hasBottom = datapoints.pointsize > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y);
+            // Series data is missing bottom points - need to format
+            if (needsBottom && !hasBottom) {
+                addBottomPoints(s, datapoints);
+            }
 
             var other = findMatchingSeries(s, plot.getData());
-            if (!other)
-                return;
+            if (!other) return;
 
             var ps = datapoints.pointsize,
                 points = datapoints.points,
@@ -70,7 +95,6 @@
                 px, py, intery, qx, qy, bottom,
                 withlines = s.lines.show,
                 horizontal = s.bars.horizontal,
-                withbottom = ps > 2 && (horizontal ? datapoints.format[2].x : datapoints.format[2].y),
                 withsteps = withlines && s.lines.steps,
                 fromgap = true,
                 keyOffset = horizontal ? 1 : 0,
@@ -78,33 +102,35 @@
                 i = 0, j = 0, l, m;
 
             while (true) {
-                if (i >= points.length)
-                    break;
+                if (i >= points.length) break;
 
                 l = newpoints.length;
 
                 if (points[i] == null) {
                     // copy gaps
-                    for (m = 0; m < ps; ++m)
+                    for (m = 0; m < ps; ++m) {
                         newpoints.push(points[i + m]);
+                    }
+
                     i += ps;
-                }
-                else if (j >= otherpoints.length) {
+                } else if (j >= otherpoints.length) {
                     // for lines, we can't use the rest of the points
                     if (!withlines) {
-                        for (m = 0; m < ps; ++m)
+                        for (m = 0; m < ps; ++m) {
                             newpoints.push(points[i + m]);
+                        }
                     }
+
                     i += ps;
-                }
-                else if (otherpoints[j] == null) {
+                } else if (otherpoints[j] == null) {
                     // oops, got a gap
-                    for (m = 0; m < ps; ++m)
+                    for (m = 0; m < ps; ++m) {
                         newpoints.push(null);
+                    }
+
                     fromgap = true;
                     j += otherps;
-                }
-                else {
+                } else {
                     // cases where we actually got two points
                     px = points[i + keyOffset];
                     py = points[i + accumulateOffset];
@@ -112,73 +138,79 @@
                     qy = otherpoints[j + accumulateOffset];
                     bottom = 0;
 
-                    if (px == qx) {
-                        for (m = 0; m < ps; ++m)
+                    if (px === qx) {
+                        for (m = 0; m < ps; ++m) {
                             newpoints.push(points[i + m]);
+                        }
 
                         newpoints[l + accumulateOffset] += qy;
                         bottom = qy;
-                        
+
                         i += ps;
                         j += otherps;
-                    }
-                    else if (px > qx) {
+                    } else if (px > qx) {
                         // we got past point below, might need to
                         // insert interpolated extra point
                         if (withlines && i > 0 && points[i - ps] != null) {
                             intery = py + (points[i - ps + accumulateOffset] - py) * (qx - px) / (points[i - ps + keyOffset] - px);
                             newpoints.push(qx);
                             newpoints.push(intery + qy);
-                            for (m = 2; m < ps; ++m)
+                            for (m = 2; m < ps; ++m) {
                                 newpoints.push(points[i + m]);
-                            bottom = qy; 
+                            }
+
+                            bottom = qy;
                         }
 
                         j += otherps;
-                    }
-                    else { // px < qx
+                    } else { // px < qx
                         if (fromgap && withlines) {
                             // if we come from a gap, we just skip this point
                             i += ps;
                             continue;
                         }
-                            
-                        for (m = 0; m < ps; ++m)
+
+                        for (m = 0; m < ps; ++m) {
                             newpoints.push(points[i + m]);
-                        
+                        }
+
                         // we might be able to interpolate a point below,
                         // this can give us a better y
-                        if (withlines && j > 0 && otherpoints[j - otherps] != null)
+                        if (withlines && j > 0 && otherpoints[j - otherps] != null) {
                             bottom = qy + (otherpoints[j - otherps + accumulateOffset] - qy) * (px - qx) / (otherpoints[j - otherps + keyOffset] - qx);
+                        }
 
                         newpoints[l + accumulateOffset] += bottom;
-                        
+
                         i += ps;
                     }
 
                     fromgap = false;
-                    
-                    if (l != newpoints.length && withbottom)
+
+                    if (l !== newpoints.length && needsBottom) {
                         newpoints[l + 2] += bottom;
+                    }
                 }
 
                 // maintain the line steps invariant
-                if (withsteps && l != newpoints.length && l > 0
-                    && newpoints[l] != null
-                    && newpoints[l] != newpoints[l - ps]
-                    && newpoints[l + 1] != newpoints[l - ps + 1]) {
-                    for (m = 0; m < ps; ++m)
+                if (withsteps && l !== newpoints.length && l > 0 &&
+                    newpoints[l] !== null &&
+                    newpoints[l] !== newpoints[l - ps] &&
+                    newpoints[l + 1] !== newpoints[l - ps + 1]) {
+                    for (m = 0; m < ps; ++m) {
                         newpoints[l + ps + m] = newpoints[l + m];
+                    }
+
                     newpoints[l + 1] = newpoints[l - ps + 1];
                 }
             }
 
             datapoints.points = newpoints;
         }
-        
+
         plot.hooks.processDatapoints.push(stackData);
     }
-    
+
     $.plot.plugins.push({
         init: init,
         options: options,
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.stack.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.stack.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,1 +1,1 @@
-!function(s){jQuery.plot.plugins.push({init:function(s){function n(s,n){for(var t=null,i=0;i<n.length&&s!=n[i];++i)n[i].stack==s.stack&&(t=n[i]);return t}s.hooks.processDatapoints.push(function(s,t,i){if(null!=t.stack&&!1!==t.stack){var l=n(t,s.getData());if(l){for(var o,e,u,f,p,a,r,h,c=i.pointsize,g=i.points,k=l.datapoints.pointsize,v=l.datapoints.points,m=[],z=t.lines.show,d=t.bars.horizontal,y=c>2&&(d?i.format[2].x:i.format[2].y),D=z&&t.lines.steps,b=!0,j=d?1:0,w=d?0:1,x=0,Q=0;!(x>=g.length);){if(r=m.length,null==g[x]){for(h=0;h<c;++h)m.push(g[x+h]);x+=c}else if(Q>=v.length){if(!z)for(h=0;h<c;++h)m.push(g[x+h]);x+=c}else if(null==v[Q]){for(h=0;h<c;++h)m.push(null);b=!0,Q+=k}else{if(o=g[x+j],e=g[x+w],f=v[Q+j],p=v[Q+w],a=0,o==f){for(h=0;h<c;++h)m.push(g[x+h]);m[r+w]+=p,a=p,x+=c,Q+=k}else if(o>f){if(z&&x>0&&null!=g[x-c]){for(u=e+(g[x-c+w]-e)*(f-o)/(g[x-c+j]-o),m.push(f),m.push(u+p),h=2;h<c;++h)m.push(g[x+h]);a=p}Q+=k}else{if(b&&z){x+=c;continue}for(h=0;h<c;++h)m.push(g[x+h]);z&&Q>0&&null!=v[Q-k]&&(a=p+(v[Q-k+w]-p)*(o-f)/(v[Q-k+j]-f)),m[r+w]+=a,x+=c}b=!1,r!=m.length&&y&&(m[r+2]+=a)}if(D&&r!=m.length&&r>0&&null!=m[r]&&m[r]!=m[r-c]&&m[r+1]!=m[r-c+1]){for(h=0;h<c;++h)m[r+c+h]=m[r+h];m[r+1]=m[r-c+1]}}i.points=m}}})},options:{series:{stack:null}},name:"stack",version:"1.2"})}();
+!function(s){jQuery.plot.plugins.push({init:function(s){function n(s,n){for(var t=null,i=0;i<n.length&&s!==n[i];++i)n[i].stack===s.stack&&(t=n[i]);return t}function t(s,n){for(var t=[],i=0;i<n.points.length;i+=2)t.push(n.points[i]),t.push(n.points[i+1]),t.push(0);n.format.push({x:!1,y:!0,number:!0,required:!1,computeRange:"none"!==s.yaxis.options.autoScale,defaultValue:0}),n.points=t,n.pointsize=3}s.hooks.processDatapoints.push(function(s,i,o){if(null!=i.stack&&!1!==i.stack){var e=i.bars.show||i.lines.show&&i.lines.fill,l=o.pointsize>2&&(w?o.format[2].x:o.format[2].y);e&&!l&&t(i,o);var u=n(i,s.getData());if(u){for(var p,a,f,r,h,c,g,k,m=o.pointsize,v=o.points,z=u.datapoints.pointsize,d=u.datapoints.points,y=[],b=i.lines.show,w=i.bars.horizontal,x=b&&i.lines.steps,D=!0,j=w?1:0,q=w?0:1,Q=0,R=0;!(Q>=v.length);){if(g=y.length,null==v[Q]){for(k=0;k<m;++k)y.push(v[Q+k]);Q+=m}else if(R>=d.length){if(!b)for(k=0;k<m;++k)y.push(v[Q+k]);Q+=m}else if(null==d[R]){for(k=0;k<m;++k)y.push(null);D=!0,R+=z}else{if(p=v[Q+j],a=v[Q+q],r=d[R+j],h=d[R+q],c=0,p===r){for(k=0;k<m;++k)y.push(v[Q+k]);y[g+q]+=h,c=h,Q+=m,R+=z}else if(p>r){if(b&&Q>0&&null!=v[Q-m]){for(f=a+(v[Q-m+q]-a)*(r-p)/(v[Q-m+j]-p),y.push(r),y.push(f+h),k=2;k<m;++k)y.push(v[Q+k]);c=h}R+=z}else{if(D&&b){Q+=m;continue}for(k=0;k<m;++k)y.push(v[Q+k]);b&&R>0&&null!=d[R-z]&&(c=h+(d[R-z+q]-h)*(p-r)/(d[R-z+j]-r)),y[g+q]+=c,Q+=m}D=!1,g!==y.length&&e&&(y[g+2]+=c)}if(x&&g!==y.length&&g>0&&null!==y[g]&&y[g]!==y[g-m]&&y[g+1]!==y[g-m+1]){for(k=0;k<m;++k)y[g+m+k]=y[g+k];y[g+1]=y[g-m+1]}}o.points=y}}})},options:{series:{stack:null}},name:"stack",version:"1.2"})}();
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.symbol.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.symbol.js	Wed Oct 16 13:00:43 2019 +0200
@@ -5,64 +5,91 @@
 
 The symbols are accessed as strings through the standard symbol options:
 
-	series: {
-		points: {
-			symbol: "square" // or "diamond", "triangle", "cross"
-		}
-	}
+    series: {
+        points: {
+            symbol: "square" // or "diamond", "triangle", "cross", "plus", "ellipse", "rectangle"
+        }
+    }
 
 */
 
 (function ($) {
-    function processRawData(plot, series, datapoints) {
-        // we normalize the area of each symbol so it is approximately the
-        // same as a circle of the given radius
+    // we normalize the area of each symbol so it is approximately the
+    // same as a circle of the given radius
 
-        var handlers = {
-            square: function (ctx, x, y, radius, shadow) {
-                // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2
-                var size = radius * Math.sqrt(Math.PI) / 2;
-                ctx.rect(x - size, y - size, size + size, size + size);
-            },
-            diamond: function (ctx, x, y, radius, shadow) {
-                // pi * r^2 = 2s^2  =>  s = r * sqrt(pi/2)
-                var size = radius * Math.sqrt(Math.PI / 2);
-                ctx.moveTo(x - size, y);
-                ctx.lineTo(x, y - size);
-                ctx.lineTo(x + size, y);
-                ctx.lineTo(x, y + size);
-                ctx.lineTo(x - size, y);
-            },
-            triangle: function (ctx, x, y, radius, shadow) {
-                // pi * r^2 = 1/2 * s^2 * sin (pi / 3)  =>  s = r * sqrt(2 * pi / sin(pi / 3))
-                var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3));
-                var height = size * Math.sin(Math.PI / 3);
-                ctx.moveTo(x - size/2, y + height/2);
-                ctx.lineTo(x + size/2, y + height/2);
-                if (!shadow) {
-                    ctx.lineTo(x, y - height/2);
-                    ctx.lineTo(x - size/2, y + height/2);
-                }
-            },
-            cross: function (ctx, x, y, radius, shadow) {
-                // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2
-                var size = radius * Math.sqrt(Math.PI) / 2;
-                ctx.moveTo(x - size, y - size);
-                ctx.lineTo(x + size, y + size);
-                ctx.moveTo(x - size, y + size);
-                ctx.lineTo(x + size, y - size);
+    var square = function (ctx, x, y, radius, shadow) {
+            // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2
+            var size = radius * Math.sqrt(Math.PI) / 2;
+            ctx.rect(x - size, y - size, size + size, size + size);
+        },
+        rectangle = function (ctx, x, y, radius, shadow) {
+            // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2
+            var size = radius * Math.sqrt(Math.PI) / 2;
+            ctx.rect(x - size, y - size, size + size, size + size);
+        },
+        diamond = function (ctx, x, y, radius, shadow) {
+            // pi * r^2 = 2s^2  =>  s = r * sqrt(pi/2)
+            var size = radius * Math.sqrt(Math.PI / 2);
+            ctx.moveTo(x - size, y);
+            ctx.lineTo(x, y - size);
+            ctx.lineTo(x + size, y);
+            ctx.lineTo(x, y + size);
+            ctx.lineTo(x - size, y);
+            ctx.lineTo(x, y - size);
+        },
+        triangle = function (ctx, x, y, radius, shadow) {
+            // pi * r^2 = 1/2 * s^2 * sin (pi / 3)  =>  s = r * sqrt(2 * pi / sin(pi / 3))
+            var size = radius * Math.sqrt(2 * Math.PI / Math.sin(Math.PI / 3));
+            var height = size * Math.sin(Math.PI / 3);
+            ctx.moveTo(x - size / 2, y + height / 2);
+            ctx.lineTo(x + size / 2, y + height / 2);
+            if (!shadow) {
+                ctx.lineTo(x, y - height / 2);
+                ctx.lineTo(x - size / 2, y + height / 2);
+                ctx.lineTo(x + size / 2, y + height / 2);
             }
+        },
+        cross = function (ctx, x, y, radius, shadow) {
+            // pi * r^2 = (2s)^2  =>  s = r * sqrt(pi)/2
+            var size = radius * Math.sqrt(Math.PI) / 2;
+            ctx.moveTo(x - size, y - size);
+            ctx.lineTo(x + size, y + size);
+            ctx.moveTo(x - size, y + size);
+            ctx.lineTo(x + size, y - size);
+        },
+        ellipse = function(ctx, x, y, radius, shadow, fill) {
+            if (!shadow) {
+                ctx.moveTo(x + radius, y);
+                ctx.arc(x, y, radius, 0, Math.PI * 2, false);
+            }
+        },
+        plus = function (ctx, x, y, radius, shadow) {
+            var size = radius * Math.sqrt(Math.PI / 2);
+            ctx.moveTo(x - size, y);
+            ctx.lineTo(x + size, y);
+            ctx.moveTo(x, y + size);
+            ctx.lineTo(x, y - size);
+        },
+        handlers = {
+            square: square,
+            rectangle: rectangle,
+            diamond: diamond,
+            triangle: triangle,
+            cross: cross,
+            ellipse: ellipse,
+            plus: plus
         };
 
-        var s = series.points.symbol;
-        if (handlers[s])
-            series.points.symbol = handlers[s];
+    square.fill = true;
+    rectangle.fill = true;
+    diamond.fill = true;
+    triangle.fill = true;
+    ellipse.fill = true;
+
+    function init(plot) {
+        plot.drawSymbol = handlers;
     }
-    
-    function init(plot) {
-        plot.hooks.processDatapoints.push(processRawData);
-    }
-    
+
     $.plot.plugins.push({
         init: init,
         name: 'symbols',
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.symbol.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.symbol.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,7 +1,1 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
-
-Copyright (c) 2007-2014 IOLA and Ole Laursen.
-Licensed under the MIT license.
-
-*/
-(function($){function processRawData(plot,series,datapoints){var handlers={square:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.rect(x-size,y-size,size+size,size+size)},diamond:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI/2);ctx.moveTo(x-size,y);ctx.lineTo(x,y-size);ctx.lineTo(x+size,y);ctx.lineTo(x,y+size);ctx.lineTo(x-size,y)},triangle:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3));var height=size*Math.sin(Math.PI/3);ctx.moveTo(x-size/2,y+height/2);ctx.lineTo(x+size/2,y+height/2);if(!shadow){ctx.lineTo(x,y-height/2);ctx.lineTo(x-size/2,y+height/2)}},cross:function(ctx,x,y,radius,shadow){var size=radius*Math.sqrt(Math.PI)/2;ctx.moveTo(x-size,y-size);ctx.lineTo(x+size,y+size);ctx.moveTo(x-size,y+size);ctx.lineTo(x+size,y-size)}};var s=series.points.symbol;if(handlers[s])series.points.symbol=handlers[s]}function init(plot){plot.hooks.processDatapoints.push(processRawData)}$.plot.plugins.push({init:init,name:"symbols",version:"1.0"})})(jQuery);
\ No newline at end of file
+!function(o){var n=function(o,n,t,i,l){var a=i*Math.sqrt(Math.PI)/2;o.rect(n-a,t-a,a+a,a+a)},t=function(o,n,t,i,l){var a=i*Math.sqrt(Math.PI)/2;o.rect(n-a,t-a,a+a,a+a)},i=function(o,n,t,i,l){var a=i*Math.sqrt(Math.PI/2);o.moveTo(n-a,t),o.lineTo(n,t-a),o.lineTo(n+a,t),o.lineTo(n,t+a),o.lineTo(n-a,t),o.lineTo(n,t-a)},l=function(o,n,t,i,l){var a=i*Math.sqrt(2*Math.PI/Math.sin(Math.PI/3)),e=a*Math.sin(Math.PI/3);o.moveTo(n-a/2,t+e/2),o.lineTo(n+a/2,t+e/2),l||(o.lineTo(n,t-e/2),o.lineTo(n-a/2,t+e/2),o.lineTo(n+a/2,t+e/2))},a=function(o,n,t,i,l,a){l||(o.moveTo(n+i,t),o.arc(n,t,i,0,2*Math.PI,!1))},e={square:n,rectangle:t,diamond:i,triangle:l,cross:function(o,n,t,i,l){var a=i*Math.sqrt(Math.PI)/2;o.moveTo(n-a,t-a),o.lineTo(n+a,t+a),o.moveTo(n-a,t+a),o.lineTo(n+a,t-a)},ellipse:a,plus:function(o,n,t,i,l){var a=i*Math.sqrt(Math.PI/2);o.moveTo(n-a,t),o.lineTo(n+a,t),o.moveTo(n,t+a),o.lineTo(n,t-a)}};n.fill=!0,t.fill=!0,i.fill=!0,l.fill=!0,a.fill=!0,o.plot.plugins.push({init:function(o){o.drawSymbol=e},name:"symbols",version:"1.0"})}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.threshold.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.threshold.js	Wed Oct 16 13:00:43 2019 +0200
@@ -5,29 +5,29 @@
 
 The plugin supports these options:
 
-	series: {
-		threshold: {
-			below: number
-			color: colorspec
-		}
-	}
+    series: {
+        threshold: {
+            below: number
+            color: colorspec
+        }
+    }
 
 It can also be applied to a single series, like this:
 
-	$.plot( $("#placeholder"), [{
-		data: [ ... ],
-		threshold: { ... }
-	}])
+    $.plot( $("#placeholder"), [{
+        data: [ ... ],
+        threshold: { ... }
+    }])
 
 An array can be passed for multiple thresholding, like this:
 
-	threshold: [{
-		below: number1
-		color: color1
-	},{
-		below: number2
-		color: color2
-	}]
+    threshold: [{
+        below: number1
+        color: color1
+    },{
+        below: number2
+        color: color2
+    }]
 
 These multiple threshold objects can be passed in any order since they are
 sorted by the processing function.
@@ -46,7 +46,7 @@
     var options = {
         series: { threshold: null } // or { below: number, color: color spec}
     };
-    
+
     function init(plot) {
         function thresholdData(plot, s, datapoints, below, color) {
             var ps = datapoints.pointsize, i, x, y, p, prevp,
@@ -58,7 +58,7 @@
             thresholded.threshold = null;
             thresholded.originSeries = s;
             thresholded.data = [];
- 
+
             var origpoints = datapoints.points,
                 addCrossingPoints = s.lines.show;
 
@@ -71,68 +71,69 @@
                 y = origpoints[i + 1];
 
                 prevp = p;
-                if (y < below)
-                    p = threspoints;
-                else
-                    p = newpoints;
+                if (y < below) p = threspoints;
+                else p = newpoints;
 
-                if (addCrossingPoints && prevp != p && x != null
-                    && i > 0 && origpoints[i - ps] != null) {
+                if (addCrossingPoints && prevp !== p &&
+                    x !== null && i > 0 &&
+                    origpoints[i - ps] != null) {
                     var interx = x + (below - y) * (x - origpoints[i - ps]) / (y - origpoints[i - ps + 1]);
                     prevp.push(interx);
                     prevp.push(below);
-                    for (m = 2; m < ps; ++m)
+                    for (m = 2; m < ps; ++m) {
                         prevp.push(origpoints[i + m]);
-                    
+                    }
+
                     p.push(null); // start new segment
                     p.push(null);
-                    for (m = 2; m < ps; ++m)
+                    for (m = 2; m < ps; ++m) {
                         p.push(origpoints[i + m]);
+                    }
+
                     p.push(interx);
                     p.push(below);
-                    for (m = 2; m < ps; ++m)
+                    for (m = 2; m < ps; ++m) {
                         p.push(origpoints[i + m]);
+                    }
                 }
 
                 p.push(x);
                 p.push(y);
-                for (m = 2; m < ps; ++m)
+                for (m = 2; m < ps; ++m) {
                     p.push(origpoints[i + m]);
+                }
             }
 
             datapoints.points = newpoints;
             thresholded.datapoints.points = threspoints;
-            
+
             if (thresholded.datapoints.points.length > 0) {
                 var origIndex = $.inArray(s, plot.getData());
                 // Insert newly-generated series right after original one (to prevent it from becoming top-most)
                 plot.getData().splice(origIndex + 1, 0, thresholded);
             }
-                
+
             // FIXME: there are probably some edge cases left in bars
         }
-        
+
         function processThresholds(plot, s, datapoints) {
-            if (!s.threshold)
-                return;
-            
+            if (!s.threshold) return;
             if (s.threshold instanceof Array) {
                 s.threshold.sort(function(a, b) {
                     return a.below - b.below;
                 });
-                
+
                 $(s.threshold).each(function(i, th) {
                     thresholdData(plot, s, datapoints, th.below, th.color);
                 });
-            }
-            else {
+            } else {
                 thresholdData(plot, s, datapoints, s.threshold.below, s.threshold.color);
             }
         }
-        
+
         plot.hooks.processDatapoints.push(processThresholds);
     }
-    
+
     $.plot.plugins.push({
         init: init,
         options: options,
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.threshold.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.threshold.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,1 +1,1 @@
-!function(o){o.plot.plugins.push({init:function(s){function t(s,t,n,l,h){var r,e,i,p,u,a=n.pointsize,f=o.extend({},t);f.datapoints={points:[],pointsize:a,format:n.format},f.label=null,f.color=h,f.threshold=null,f.originSeries=t,f.data=[];var d,c=n.points,g=t.lines.show,b=[],v=[];for(r=0;r<c.length;r+=a){if(e=c[r],i=c[r+1],u=p,p=i<l?b:v,g&&u!=p&&null!=e&&r>0&&null!=c[r-a]){var w=e+(l-i)*(e-c[r-a])/(i-c[r-a+1]);for(u.push(w),u.push(l),d=2;d<a;++d)u.push(c[r+d]);for(p.push(null),p.push(null),d=2;d<a;++d)p.push(c[r+d]);for(p.push(w),p.push(l),d=2;d<a;++d)p.push(c[r+d])}for(p.push(e),p.push(i),d=2;d<a;++d)p.push(c[r+d])}if(n.points=v,f.datapoints.points=b,f.datapoints.points.length>0){var m=o.inArray(t,s.getData());s.getData().splice(m+1,0,f)}}s.hooks.processDatapoints.push(function(s,n,l){n.threshold&&(n.threshold instanceof Array?(n.threshold.sort(function(o,s){return o.below-s.below}),o(n.threshold).each(function(o,h){t(s,n,l,h.below,h.color)})):t(s,n,l,n.threshold.below,n.threshold.color))})},options:{series:{threshold:null}},name:"threshold",version:"1.2"})}(jQuery);
+!function(o){o.plot.plugins.push({init:function(s){function t(s,t,n,l,h){var r,e,i,p,u,a=n.pointsize,f=o.extend({},t);f.datapoints={points:[],pointsize:a,format:n.format},f.label=null,f.color=h,f.threshold=null,f.originSeries=t,f.data=[];var d,c=n.points,g=t.lines.show,b=[],v=[];for(r=0;r<c.length;r+=a){if(e=c[r],i=c[r+1],u=p,p=i<l?b:v,g&&u!==p&&null!==e&&r>0&&null!=c[r-a]){var w=e+(l-i)*(e-c[r-a])/(i-c[r-a+1]);for(u.push(w),u.push(l),d=2;d<a;++d)u.push(c[r+d]);for(p.push(null),p.push(null),d=2;d<a;++d)p.push(c[r+d]);for(p.push(w),p.push(l),d=2;d<a;++d)p.push(c[r+d])}for(p.push(e),p.push(i),d=2;d<a;++d)p.push(c[r+d])}if(n.points=v,f.datapoints.points=b,f.datapoints.points.length>0){var m=o.inArray(t,s.getData());s.getData().splice(m+1,0,f)}}s.hooks.processDatapoints.push(function(s,n,l){n.threshold&&(n.threshold instanceof Array?(n.threshold.sort(function(o,s){return o.below-s.below}),o(n.threshold).each(function(o,h){t(s,n,l,h.below,h.color)})):t(s,n,l,n.threshold.below,n.threshold.color))})},options:{series:{threshold:null}},name:"threshold",version:"1.2"})}(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.time.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.time.js	Wed Oct 16 13:00:43 2019 +0200
@@ -5,56 +5,113 @@
 
 Set axis.mode to "time" to enable. See the section "Time series data" in
 API.txt for details.
-
 */
 
 (function($) {
+    'use strict';
 
-	var options = {
-		xaxis: {
-			timezone: null,		// "browser" for local to the client or timezone for timezone-js
-			timeformat: null,	// format string to use
-			twelveHourClock: false,	// 12 or 24 time in time mode
-			monthNames: null	// list of names of months
-		}
-	};
+    var options = {
+        xaxis: {
+            timezone: null, // "browser" for local to the client or timezone for timezone-js
+            timeformat: null, // format string to use
+            twelveHourClock: false, // 12 or 24 time in time mode
+            monthNames: null, // list of names of months
+            timeBase: 'seconds' // are the values in given in mircoseconds, milliseconds or seconds
+        },
+        yaxis: {
+            timeBase: 'seconds'
+        }
+    };
+
+    var floorInBase = $.plot.saturated.floorInBase;
 
-	// round to nearby lower multiple of base
+    // Method to provide microsecond support to Date like classes.
+    var CreateMicroSecondDate = function(dateType, microEpoch) {
+        var newDate = new dateType(microEpoch);
+
+        var oldSetTime = newDate.setTime.bind(newDate);
+        newDate.update = function(microEpoch) {
+            oldSetTime(microEpoch);
+
+            // Round epoch to 3 decimal accuracy
+            microEpoch = Math.round(microEpoch*1000)/1000;
+
+            // Microseconds are stored as integers
+            var seconds = microEpoch/1000;
+            this.microseconds = 1000000 * (seconds - Math.floor(seconds));
+        };
 
-	function floorInBase(n, base) {
-		return base * Math.floor(n / base);
-	}
+        var oldGetTime = newDate.getTime.bind(newDate);
+        newDate.getTime = function () {
+            var microEpoch = oldGetTime() + this.microseconds / 1000;
+            return microEpoch;
+        };
 
-	// Returns a string with the date d formatted according to fmt.
-	// A subset of the Open Group's strftime format is supported.
+        newDate.setTime = function (microEpoch) {
+            this.update(microEpoch);
+        };
+
+        newDate.getMicroseconds = function() {
+            return this.microseconds;
+        };
 
-	function formatDate(d, fmt, monthNames, dayNames) {
+        newDate.setMicroseconds = function(microseconds) {
+            // Replace the microsecond part (6 last digits) in microEpoch
+            var epochWithoutMicroseconds = oldGetTime();
+            var newEpoch = epochWithoutMicroseconds + microseconds/1000;
+            this.update(newEpoch);
+        };
+
+        newDate.setUTCMicroseconds = function(microseconds) { this.setMicroseconds(microseconds); }
+
+        newDate.getUTCMicroseconds = function() { return this.getMicroseconds(); }
 
-		if (typeof d.strftime == "function") {
-			return d.strftime(fmt);
-		}
+        newDate.microseconds = null;
+        newDate.microEpoch = null;
+        newDate.update(microEpoch);
+        return newDate;
+    }
 
-		var leftPad = function(n, pad) {
+    // Returns a string with the date d formatted according to fmt.
+    // A subset of the Open Group's strftime format is supported.
+
+    function formatDate(d, fmt, monthNames, dayNames) {
+        if (typeof d.strftime === "function") {
+            return d.strftime(fmt);
+        }
+
+        var leftPad = function(n, pad) {
 			n = "" + n;
 			pad = "" + (pad == null ? "0" : pad);
 			return n.length == 1 ? pad + n : n;
 		};
 
-		var r = [];
-		var escape = false;
-		var hours = d.getHours();
+		var formatMicroseconds = function(n, dec) {
+			if (dec < 6 && dec > 0) {
+				var magnitude = parseFloat('1e' + (dec-6));
+				n = Math.round(Math.round(n*magnitude)/magnitude);
+				n = ('00000' + n).slice(-6,-(6 - dec));
+			} else {
+                n = Math.round(n)
+				n = ('00000' + n).slice(-6);
+			}
+			return n;
+		};
+
+        var r = [];
+        var escape = false;
+        var hours = d.getHours();
 		var isAM = hours < 12;
 
-		if (monthNames == null) {
-			monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
-		}
+        if (!monthNames) {
+            monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
+        }
 
-		if (dayNames == null) {
-			dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
-		}
+        if (!dayNames) {
+            dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+        }
 
-		var hours12;
-
+        var hours12;
 		if (hours > 12) {
 			hours12 = hours - 12;
 		} else if (hours == 0) {
@@ -63,11 +120,13 @@
 			hours12 = hours;
 		}
 
-		for (var i = 0; i < fmt.length; ++i) {
-
+        var decimals = -1;
+        for (var i = 0; i < fmt.length; ++i) {
 			var c = fmt.charAt(i);
 
-			if (escape) {
+            if (!isNaN(Number(c)) && Number(c) > 0) {
+                decimals = Number(c);
+            } else if (escape) {
 				switch (c) {
 					case 'a': c = "" + dayNames[d.getDay()]; break;
 					case 'b': c = "" + monthNames[d.getMonth()]; break;
@@ -83,6 +142,7 @@
 					case 'q':
 						c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
 					case 'S': c = leftPad(d.getSeconds()); break;
+					case 's': c = "" + formatMicroseconds(d.getMicroseconds(),decimals); break;
 					case 'y': c = leftPad(d.getFullYear() % 100); break;
 					case 'Y': c = "" + d.getFullYear(); break;
 					case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
@@ -101,285 +161,377 @@
 		}
 
 		return r.join("");
-	}
+    }
 
-	// To have a consistent view of time-based data independent of which time
-	// zone the client happens to be in we need a date-like object independent
-	// of time zones.  This is done through a wrapper that only calls the UTC
-	// versions of the accessor methods.
+    // To have a consistent view of time-based data independent of which time
+    // zone the client happens to be in we need a date-like object independent
+    // of time zones.  This is done through a wrapper that only calls the UTC
+    // versions of the accessor methods.
 
-	function makeUtcWrapper(d) {
-
-		function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
-			sourceObj[sourceMethod] = function() {
-				return targetObj[targetMethod].apply(targetObj, arguments);
-			};
-		};
+    function makeUtcWrapper(d) {
+        function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
+            sourceObj[sourceMethod] = function() {
+                return targetObj[targetMethod].apply(targetObj, arguments);
+            };
+        }
 
-		var utc = {
-			date: d
-		};
+        var utc = {
+            date: d
+        };
 
-		// support strftime, if found
+        // support strftime, if found
+        if (d.strftime !== undefined) {
+            addProxyMethod(utc, "strftime", d, "strftime");
+        }
 
-		if (d.strftime != undefined) {
-			addProxyMethod(utc, "strftime", d, "strftime");
-		}
+        addProxyMethod(utc, "getTime", d, "getTime");
+        addProxyMethod(utc, "setTime", d, "setTime");
+
+        var props = ["Date", "Day", "FullYear", "Hours", "Minutes", "Month", "Seconds", "Milliseconds", "Microseconds"];
 
-		addProxyMethod(utc, "getTime", d, "getTime");
-		addProxyMethod(utc, "setTime", d, "setTime");
-
-		var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];
+        for (var p = 0; p < props.length; p++) {
+            addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
+            addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
+        }
 
-		for (var p = 0; p < props.length; p++) {
-			addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
-			addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
-		}
+        return utc;
+    }
 
-		return utc;
-	};
+    // select time zone strategy.  This returns a date-like object tied to the
+    // desired timezone
+    function dateGenerator(ts, opts) {
+        var maxDateValue = 8640000000000000;
 
-	// select time zone strategy.  This returns a date-like object tied to the
-	// desired timezone
+        if (opts && opts.timeBase === 'seconds') {
+            ts *= 1000;
+        } else if (opts.timeBase === 'microseconds') {
+            ts /= 1000;
+        }
 
-	function dateGenerator(ts, opts) {
-		if (opts.timezone == "browser") {
-			return new Date(ts);
-		} else if (!opts.timezone || opts.timezone == "utc") {
-			return makeUtcWrapper(new Date(ts));
-		} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
-			var d = new timezoneJS.Date();
-			// timezone-js is fickle, so be sure to set the time zone before
-			// setting the time.
-			d.setTimezone(opts.timezone);
-			d.setTime(ts);
-			return d;
-		} else {
-			return makeUtcWrapper(new Date(ts));
-		}
-	}
-	
-	// map of app. size of time units in milliseconds
+        if (ts > maxDateValue) {
+            ts = maxDateValue;
+        } else if (ts < -maxDateValue) {
+            ts = -maxDateValue;
+        }
+
+        if (opts.timezone === "browser") {
+            return CreateMicroSecondDate(Date, ts);
+        } else if (!opts.timezone || opts.timezone === "utc") {
+            return makeUtcWrapper(CreateMicroSecondDate(Date, ts));
+        } else if (typeof timezoneJS !== "undefined" && typeof timezoneJS.Date !== "undefined") {
+            var d = CreateMicroSecondDate(timezoneJS.Date, ts);
+            // timezone-js is fickle, so be sure to set the time zone before
+            // setting the time.
+            d.setTimezone(opts.timezone);
+            d.setTime(ts);
+            return d;
+        } else {
+            return makeUtcWrapper(CreateMicroSecondDate(Date, ts));
+        }
+    }
 
-	var timeUnitSize = {
-		"second": 1000,
-		"minute": 60 * 1000,
-		"hour": 60 * 60 * 1000,
-		"day": 24 * 60 * 60 * 1000,
-		"month": 30 * 24 * 60 * 60 * 1000,
-		"quarter": 3 * 30 * 24 * 60 * 60 * 1000,
-		"year": 365.2425 * 24 * 60 * 60 * 1000
-	};
-
-	// the allowed tick sizes, after 1 year we use
-	// an integer algorithm
+    // map of app. size of time units in seconds
+    var timeUnitSizeSeconds = {
+        "microsecond": 0.000001,
+        "millisecond": 0.001,
+        "second": 1,
+        "minute": 60,
+        "hour": 60 * 60,
+        "day": 24 * 60 * 60,
+        "month": 30 * 24 * 60 * 60,
+        "quarter": 3 * 30 * 24 * 60 * 60,
+        "year": 365.2425 * 24 * 60 * 60
+    };
 
-	var baseSpec = [
-		[1, "second"], [2, "second"], [5, "second"], [10, "second"],
-		[30, "second"], 
-		[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
-		[30, "minute"], 
-		[1, "hour"], [2, "hour"], [4, "hour"],
-		[8, "hour"], [12, "hour"],
-		[1, "day"], [2, "day"], [3, "day"],
-		[0.25, "month"], [0.5, "month"], [1, "month"],
-		[2, "month"]
-	];
+    // map of app. size of time units in milliseconds
+    var timeUnitSizeMilliseconds = {
+        "microsecond": 0.001,
+        "millisecond": 1,
+        "second": 1000,
+        "minute": 60 * 1000,
+        "hour": 60 * 60 * 1000,
+        "day": 24 * 60 * 60 * 1000,
+        "month": 30 * 24 * 60 * 60 * 1000,
+        "quarter": 3 * 30 * 24 * 60 * 60 * 1000,
+        "year": 365.2425 * 24 * 60 * 60 * 1000
+    };
 
-	// we don't know which variant(s) we'll need yet, but generating both is
-	// cheap
+    // map of app. size of time units in microseconds
+    var timeUnitSizeMicroseconds = {
+        "microsecond": 1,
+        "millisecond": 1000,
+        "second": 1000000,
+        "minute": 60 * 1000000,
+        "hour": 60 * 60 * 1000000,
+        "day": 24 * 60 * 60 * 1000000,
+        "month": 30 * 24 * 60 * 60 * 1000000,
+        "quarter": 3 * 30 * 24 * 60 * 60 * 1000000,
+        "year": 365.2425 * 24 * 60 * 60 * 1000000
+    };
 
-	var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
-		[1, "year"]]);
-	var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
-		[1, "year"]]);
+    // the allowed tick sizes, after 1 year we use
+    // an integer algorithm
 
-	function init(plot) {
-		plot.hooks.processOptions.push(function (plot, options) {
-			$.each(plot.getAxes(), function(axisName, axis) {
-
-				var opts = axis.options;
+    var baseSpec = [
+        [1, "microsecond"], [2, "microsecond"], [5, "microsecond"], [10, "microsecond"],
+        [25, "microsecond"], [50, "microsecond"], [100, "microsecond"], [250, "microsecond"], [500, "microsecond"],
+        [1, "millisecond"], [2, "millisecond"], [5, "millisecond"], [10, "millisecond"],
+        [25, "millisecond"], [50, "millisecond"], [100, "millisecond"], [250, "millisecond"], [500, "millisecond"],
+        [1, "second"], [2, "second"], [5, "second"], [10, "second"],
+        [30, "second"],
+        [1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
+        [30, "minute"],
+        [1, "hour"], [2, "hour"], [4, "hour"],
+        [8, "hour"], [12, "hour"],
+        [1, "day"], [2, "day"], [3, "day"],
+        [0.25, "month"], [0.5, "month"], [1, "month"],
+        [2, "month"]
+    ];
 
-				if (opts.mode == "time") {
-					axis.tickGenerator = function(axis) {
-
-						var ticks = [];
-						var d = dateGenerator(axis.min, opts);
-						var minSize = 0;
-
-						// make quarter use a possibility if quarters are
-						// mentioned in either of these options
+    // we don't know which variant(s) we'll need yet, but generating both is
+    // cheap
 
-						var spec = (opts.tickSize && opts.tickSize[1] ===
-							"quarter") ||
-							(opts.minTickSize && opts.minTickSize[1] ===
-							"quarter") ? specQuarters : specMonths;
+    var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
+        [1, "year"]]);
+    var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
+        [1, "year"]]);
+
+
+    function dateTickGenerator(axis) {
+        var opts = axis.options,
+            ticks = [],
+            d = dateGenerator(axis.min, opts),
+            minSize = 0;
 
-						if (opts.minTickSize != null) {
-							if (typeof opts.tickSize == "number") {
-								minSize = opts.tickSize;
-							} else {
-								minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
-							}
-						}
+        // make quarter use a possibility if quarters are
+        // mentioned in either of these options
+        var spec = (opts.tickSize && opts.tickSize[1] ===
+            "quarter") ||
+            (opts.minTickSize && opts.minTickSize[1] ===
+            "quarter") ? specQuarters : specMonths;
 
-						for (var i = 0; i < spec.length - 1; ++i) {
-							if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
-											  + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
-								&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
-								break;
-							}
-						}
+        var timeUnitSize;
+        if (opts.timeBase === 'seconds') {
+            timeUnitSize = timeUnitSizeSeconds;
+        } else if (opts.timeBase === 'microseconds') {
+            timeUnitSize = timeUnitSizeMicroseconds;
+        } else {
+            timeUnitSize = timeUnitSizeMilliseconds;
+        }
 
-						var size = spec[i][0];
-						var unit = spec[i][1];
-
-						// special-case the possibility of several years
-
-						if (unit == "year") {
-
-							// if given a minTickSize in years, just use it,
-							// ensuring that it's an integer
-
-							if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
-								size = Math.floor(opts.minTickSize[0]);
-							} else {
+        if (opts.minTickSize !== null && opts.minTickSize !== undefined) {
+            if (typeof opts.tickSize === "number") {
+                minSize = opts.tickSize;
+            } else {
+                minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
+            }
+        }
 
-								var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
-								var norm = (axis.delta / timeUnitSize.year) / magn;
+        for (var i = 0; i < spec.length - 1; ++i) {
+            if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]] +
+                spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2 &&
+                spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
+                break;
+            }
+        }
 
-								if (norm < 1.5) {
-									size = 1;
-								} else if (norm < 3) {
-									size = 2;
-								} else if (norm < 7.5) {
-									size = 5;
-								} else {
-									size = 10;
-								}
+        var size = spec[i][0];
+        var unit = spec[i][1];
+        // special-case the possibility of several years
+        if (unit === "year") {
+            // if given a minTickSize in years, just use it,
+            // ensuring that it's an integer
 
-								size *= magn;
-							}
+            if (opts.minTickSize !== null && opts.minTickSize !== undefined && opts.minTickSize[1] === "year") {
+                size = Math.floor(opts.minTickSize[0]);
+            } else {
+                var magn = parseFloat('1e' + Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
+                var norm = (axis.delta / timeUnitSize.year) / magn;
 
-							// minimum size for years is 1
+                if (norm < 1.5) {
+                    size = 1;
+                } else if (norm < 3) {
+                    size = 2;
+                } else if (norm < 7.5) {
+                    size = 5;
+                } else {
+                    size = 10;
+                }
 
-							if (size < 1) {
-								size = 1;
-							}
-						}
+                size *= magn;
+            }
+
+            // minimum size for years is 1
 
-						axis.tickSize = opts.tickSize || [size, unit];
-						var tickSize = axis.tickSize[0];
-						unit = axis.tickSize[1];
-
-						var step = tickSize * timeUnitSize[unit];
+            if (size < 1) {
+                size = 1;
+            }
+        }
 
-						if (unit == "second") {
-							d.setSeconds(floorInBase(d.getSeconds(), tickSize));
-						} else if (unit == "minute") {
-							d.setMinutes(floorInBase(d.getMinutes(), tickSize));
-						} else if (unit == "hour") {
-							d.setHours(floorInBase(d.getHours(), tickSize));
-						} else if (unit == "month") {
-							d.setMonth(floorInBase(d.getMonth(), tickSize));
-						} else if (unit == "quarter") {
-							d.setMonth(3 * floorInBase(d.getMonth() / 3,
-								tickSize));
-						} else if (unit == "year") {
-							d.setFullYear(floorInBase(d.getFullYear(), tickSize));
-						}
+        axis.tickSize = opts.tickSize || [size, unit];
+        var tickSize = axis.tickSize[0];
+        unit = axis.tickSize[1];
+
+        var step = tickSize * timeUnitSize[unit];
 
-						// reset smaller components
+        if (unit === "microsecond") {
+            d.setMicroseconds(floorInBase(d.getMicroseconds(), tickSize));
+        } else if (unit === "millisecond") {
+            d.setMilliseconds(floorInBase(d.getMilliseconds(), tickSize));
+        } else if (unit === "second") {
+            d.setSeconds(floorInBase(d.getSeconds(), tickSize));
+        } else if (unit === "minute") {
+            d.setMinutes(floorInBase(d.getMinutes(), tickSize));
+        } else if (unit === "hour") {
+            d.setHours(floorInBase(d.getHours(), tickSize));
+        } else if (unit === "month") {
+            d.setMonth(floorInBase(d.getMonth(), tickSize));
+        } else if (unit === "quarter") {
+            d.setMonth(3 * floorInBase(d.getMonth() / 3,
+                tickSize));
+        } else if (unit === "year") {
+            d.setFullYear(floorInBase(d.getFullYear(), tickSize));
+        }
 
-						d.setMilliseconds(0);
+        // reset smaller components
 
-						if (step >= timeUnitSize.minute) {
-							d.setSeconds(0);
-						}
-						if (step >= timeUnitSize.hour) {
-							d.setMinutes(0);
-						}
-						if (step >= timeUnitSize.day) {
-							d.setHours(0);
-						}
-						if (step >= timeUnitSize.day * 4) {
-							d.setDate(1);
-						}
-						if (step >= timeUnitSize.month * 2) {
-							d.setMonth(floorInBase(d.getMonth(), 3));
-						}
-						if (step >= timeUnitSize.quarter * 2) {
-							d.setMonth(floorInBase(d.getMonth(), 6));
-						}
-						if (step >= timeUnitSize.year) {
-							d.setMonth(0);
-						}
+        if (step >= timeUnitSize.millisecond) {
+            if (step >= timeUnitSize.second) {
+                d.setMicroseconds(0);
+            } else {
+                d.setMicroseconds(d.getMilliseconds()*1000);
+            }
+        }
+        if (step >= timeUnitSize.minute) {
+            d.setSeconds(0);
+        }
+        if (step >= timeUnitSize.hour) {
+            d.setMinutes(0);
+        }
+        if (step >= timeUnitSize.day) {
+            d.setHours(0);
+        }
+        if (step >= timeUnitSize.day * 4) {
+            d.setDate(1);
+        }
+        if (step >= timeUnitSize.month * 2) {
+            d.setMonth(floorInBase(d.getMonth(), 3));
+        }
+        if (step >= timeUnitSize.quarter * 2) {
+            d.setMonth(floorInBase(d.getMonth(), 6));
+        }
+        if (step >= timeUnitSize.year) {
+            d.setMonth(0);
+        }
 
-						var carry = 0;
-						var v = Number.NaN;
-						var prev;
+        var carry = 0;
+        var v = Number.NaN;
+        var v1000;
+        var prev;
+        do {
+            prev = v;
+            v1000 = d.getTime();
+            if (opts && opts.timeBase === 'seconds') {
+                v = v1000 / 1000;
+            } else if (opts && opts.timeBase === 'microseconds') {
+                v = v1000 * 1000;
+            } else {
+                v = v1000;
+            }
 
-						do {
-
-							prev = v;
-							v = d.getTime();
-							ticks.push(v);
+            ticks.push(v);
 
-							if (unit == "month" || unit == "quarter") {
-								if (tickSize < 1) {
-
-									// a bit complicated - we'll divide the
-									// month/quarter up but we need to take
-									// care of fractions so we don't end up in
-									// the middle of a day
+            if (unit === "month" || unit === "quarter") {
+                if (tickSize < 1) {
+                    // a bit complicated - we'll divide the
+                    // month/quarter up but we need to take
+                    // care of fractions so we don't end up in
+                    // the middle of a day
+                    d.setDate(1);
+                    var start = d.getTime();
+                    d.setMonth(d.getMonth() +
+                        (unit === "quarter" ? 3 : 1));
+                    var end = d.getTime();
+                    d.setTime((v + carry * timeUnitSize.hour + (end - start) * tickSize));
+                    carry = d.getHours();
+                    d.setHours(0);
+                } else {
+                    d.setMonth(d.getMonth() +
+                        tickSize * (unit === "quarter" ? 3 : 1));
+                }
+            } else if (unit === "year") {
+                d.setFullYear(d.getFullYear() + tickSize);
+            } else {
+                if (opts.timeBase === 'seconds') {
+                    d.setTime((v + step) * 1000);
+                } else if (opts.timeBase === 'microseconds') {
+                    d.setTime((v + step) / 1000);
+                } else {
+                    d.setTime(v + step);
+                }
+            }
+        } while (v < axis.max && v !== prev);
 
-									d.setDate(1);
-									var start = d.getTime();
-									d.setMonth(d.getMonth() +
-										(unit == "quarter" ? 3 : 1));
-									var end = d.getTime();
-									d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
-									carry = d.getHours();
-									d.setHours(0);
-								} else {
-									d.setMonth(d.getMonth() +
-										tickSize * (unit == "quarter" ? 3 : 1));
-								}
-							} else if (unit == "year") {
-								d.setFullYear(d.getFullYear() + tickSize);
-							} else {
-								d.setTime(v + step);
-							}
-						} while (v < axis.max && v != prev);
+        return ticks;
+    };
 
-						return ticks;
-					};
-
-					axis.tickFormatter = function (v, axis) {
+    function init(plot) {
+        plot.hooks.processOptions.push(function (plot) {
+            $.each(plot.getAxes(), function(axisName, axis) {
+                var opts = axis.options;
+                if (opts.mode === "time") {
+                    axis.tickGenerator = dateTickGenerator;
 
-						var d = dateGenerator(v, axis.options);
+                    axis.tickFormatter = function (v, axis) {
+                        var d = dateGenerator(v, axis.options);
 
-						// first check global format
-
+                        // first check global format
 						if (opts.timeformat != null) {
 							return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
 						}
 
 						// possibly use quarters if quarters are mentioned in
 						// any of these places
-
 						var useQuarters = (axis.options.tickSize &&
 								axis.options.tickSize[1] == "quarter") ||
 							(axis.options.minTickSize &&
-								axis.options.minTickSize[1] == "quarter");
+                                axis.options.minTickSize[1] == "quarter");
+
+                        var timeUnitSize;
+                        if (opts.timeBase === 'seconds') {
+                            timeUnitSize = timeUnitSizeSeconds;
+                        } else if (opts.timeBase === 'microseconds') {
+                            timeUnitSize = timeUnitSizeMicroseconds;
+                        } else {
+                            timeUnitSize = timeUnitSizeMilliseconds;
+                        }
 
 						var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
 						var span = axis.max - axis.min;
 						var suffix = (opts.twelveHourClock) ? " %p" : "";
 						var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
+                        var factor;
 						var fmt;
 
-						if (t < timeUnitSize.minute) {
+                        if (opts.timeBase === 'seconds') {
+                            factor = 1;
+                        } else if (opts.timeBase === 'microseconds') {
+                            factor = 1000000
+                        } else {
+                            factor = 1000;
+                        }
+
+                        if (t < timeUnitSize.second) {
+                            var decimals = -Math.floor(Math.log10(t/factor))
+
+                            // the two-and-halves require an additional decimal
+                            if (String(t).indexOf('25') > -1) {
+                                decimals++;
+                            }
+
+							fmt = "%S.%" + decimals + "s";
+                        } else
+                        if (t < timeUnitSize.minute) {
 							fmt = hourCode + ":%M:%S" + suffix;
 						} else if (t < timeUnitSize.day) {
 							if (span < 2 * timeUnitSize.day) {
@@ -409,24 +561,25 @@
 						var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);
 
 						return rt;
-					};
-				}
-			});
-		});
-	}
+                    };
+                }
+            });
+        });
+    }
 
-	$.plot.plugins.push({
-		init: init,
-		options: options,
-		name: 'time',
-		version: '1.0'
-	});
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: 'time',
+        version: '1.0'
+    });
 
-	// Time-axis support used to be in Flot core, which exposed the
-	// formatDate function on the plot object.  Various plugins depend
-	// on the function, so we need to re-expose it here.
+    // Time-axis support used to be in Flot core, which exposed the
+    // formatDate function on the plot object.  Various plugins depend
+    // on the function, so we need to re-expose it here.
 
-	$.plot.formatDate = formatDate;
-	$.plot.dateGenerator = dateGenerator;
-
+    $.plot.formatDate = formatDate;
+    $.plot.dateGenerator = dateGenerator;
+    $.plot.dateTickGenerator = dateTickGenerator;
+    $.plot.makeUtcWrapper = makeUtcWrapper;
 })(jQuery);
--- a/src/pyams_skin/resources/js/ext/flot/jquery.flot.time.min.js	Thu Sep 26 11:55:17 2019 +0200
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.time.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -1,7 +1,1 @@
-/* Javascript plotting library for jQuery, version 0.8.3.
-
-Copyright (c) 2007-2014 IOLA and Ole Laursen.
-Licensed under the MIT license.
-
-*/
-(function($){var options={xaxis:{timezone:null,timeformat:null,twelveHourClock:false,monthNames:null}};function floorInBase(n,base){return base*Math.floor(n/base)}function formatDate(d,fmt,monthNames,dayNames){if(typeof d.strftime=="function"){return d.strftime(fmt)}var leftPad=function(n,pad){n=""+n;pad=""+(pad==null?"0":pad);return n.length==1?pad+n:n};var r=[];var escape=false;var hours=d.getHours();var isAM=hours<12;if(monthNames==null){monthNames=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]}if(dayNames==null){dayNames=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]}var hours12;if(hours>12){hours12=hours-12}else if(hours==0){hours12=12}else{hours12=hours}for(var i=0;i<fmt.length;++i){var c=fmt.charAt(i);if(escape){switch(c){case"a":c=""+dayNames[d.getDay()];break;case"b":c=""+monthNames[d.getMonth()];break;case"d":c=leftPad(d.getDate());break;case"e":c=leftPad(d.getDate()," ");break;case"h":case"H":c=leftPad(hours);break;case"I":c=leftPad(hours12);break;case"l":c=leftPad(hours12," ");break;case"m":c=leftPad(d.getMonth()+1);break;case"M":c=leftPad(d.getMinutes());break;case"q":c=""+(Math.floor(d.getMonth()/3)+1);break;case"S":c=leftPad(d.getSeconds());break;case"y":c=leftPad(d.getFullYear()%100);break;case"Y":c=""+d.getFullYear();break;case"p":c=isAM?""+"am":""+"pm";break;case"P":c=isAM?""+"AM":""+"PM";break;case"w":c=""+d.getDay();break}r.push(c);escape=false}else{if(c=="%"){escape=true}else{r.push(c)}}}return r.join("")}function makeUtcWrapper(d){function addProxyMethod(sourceObj,sourceMethod,targetObj,targetMethod){sourceObj[sourceMethod]=function(){return targetObj[targetMethod].apply(targetObj,arguments)}}var utc={date:d};if(d.strftime!=undefined){addProxyMethod(utc,"strftime",d,"strftime")}addProxyMethod(utc,"getTime",d,"getTime");addProxyMethod(utc,"setTime",d,"setTime");var props=["Date","Day","FullYear","Hours","Milliseconds","Minutes","Month","Seconds"];for(var p=0;p<props.length;p++){addProxyMethod(utc,"get"+props[p],d,"getUTC"+props[p]);addProxyMethod(utc,"set"+props[p],d,"setUTC"+props[p])}return utc}function dateGenerator(ts,opts){if(opts.timezone=="browser"){return new Date(ts)}else if(!opts.timezone||opts.timezone=="utc"){return makeUtcWrapper(new Date(ts))}else if(typeof timezoneJS!="undefined"&&typeof timezoneJS.Date!="undefined"){var d=new timezoneJS.Date;d.setTimezone(opts.timezone);d.setTime(ts);return d}else{return makeUtcWrapper(new Date(ts))}}var timeUnitSize={second:1e3,minute:60*1e3,hour:60*60*1e3,day:24*60*60*1e3,month:30*24*60*60*1e3,quarter:3*30*24*60*60*1e3,year:365.2425*24*60*60*1e3};var baseSpec=[[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"]];var specMonths=baseSpec.concat([[3,"month"],[6,"month"],[1,"year"]]);var specQuarters=baseSpec.concat([[1,"quarter"],[2,"quarter"],[1,"year"]]);function init(plot){plot.hooks.processOptions.push(function(plot,options){$.each(plot.getAxes(),function(axisName,axis){var opts=axis.options;if(opts.mode=="time"){axis.tickGenerator=function(axis){var ticks=[];var d=dateGenerator(axis.min,opts);var minSize=0;var spec=opts.tickSize&&opts.tickSize[1]==="quarter"||opts.minTickSize&&opts.minTickSize[1]==="quarter"?specQuarters:specMonths;if(opts.minTickSize!=null){if(typeof opts.tickSize=="number"){minSize=opts.tickSize}else{minSize=opts.minTickSize[0]*timeUnitSize[opts.minTickSize[1]]}}for(var i=0;i<spec.length-1;++i){if(axis.delta<(spec[i][0]*timeUnitSize[spec[i][1]]+spec[i+1][0]*timeUnitSize[spec[i+1][1]])/2&&spec[i][0]*timeUnitSize[spec[i][1]]>=minSize){break}}var size=spec[i][0];var unit=spec[i][1];if(unit=="year"){if(opts.minTickSize!=null&&opts.minTickSize[1]=="year"){size=Math.floor(opts.minTickSize[0])}else{var magn=Math.pow(10,Math.floor(Math.log(axis.delta/timeUnitSize.year)/Math.LN10));var norm=axis.delta/timeUnitSize.year/magn;if(norm<1.5){size=1}else if(norm<3){size=2}else if(norm<7.5){size=5}else{size=10}size*=magn}if(size<1){size=1}}axis.tickSize=opts.tickSize||[size,unit];var tickSize=axis.tickSize[0];unit=axis.tickSize[1];var step=tickSize*timeUnitSize[unit];if(unit=="second"){d.setSeconds(floorInBase(d.getSeconds(),tickSize))}else if(unit=="minute"){d.setMinutes(floorInBase(d.getMinutes(),tickSize))}else if(unit=="hour"){d.setHours(floorInBase(d.getHours(),tickSize))}else if(unit=="month"){d.setMonth(floorInBase(d.getMonth(),tickSize))}else if(unit=="quarter"){d.setMonth(3*floorInBase(d.getMonth()/3,tickSize))}else if(unit=="year"){d.setFullYear(floorInBase(d.getFullYear(),tickSize))}d.setMilliseconds(0);if(step>=timeUnitSize.minute){d.setSeconds(0)}if(step>=timeUnitSize.hour){d.setMinutes(0)}if(step>=timeUnitSize.day){d.setHours(0)}if(step>=timeUnitSize.day*4){d.setDate(1)}if(step>=timeUnitSize.month*2){d.setMonth(floorInBase(d.getMonth(),3))}if(step>=timeUnitSize.quarter*2){d.setMonth(floorInBase(d.getMonth(),6))}if(step>=timeUnitSize.year){d.setMonth(0)}var carry=0;var v=Number.NaN;var prev;do{prev=v;v=d.getTime();ticks.push(v);if(unit=="month"||unit=="quarter"){if(tickSize<1){d.setDate(1);var start=d.getTime();d.setMonth(d.getMonth()+(unit=="quarter"?3:1));var end=d.getTime();d.setTime(v+carry*timeUnitSize.hour+(end-start)*tickSize);carry=d.getHours();d.setHours(0)}else{d.setMonth(d.getMonth()+tickSize*(unit=="quarter"?3:1))}}else if(unit=="year"){d.setFullYear(d.getFullYear()+tickSize)}else{d.setTime(v+step)}}while(v<axis.max&&v!=prev);return ticks};axis.tickFormatter=function(v,axis){var d=dateGenerator(v,axis.options);if(opts.timeformat!=null){return formatDate(d,opts.timeformat,opts.monthNames,opts.dayNames)}var useQuarters=axis.options.tickSize&&axis.options.tickSize[1]=="quarter"||axis.options.minTickSize&&axis.options.minTickSize[1]=="quarter";var t=axis.tickSize[0]*timeUnitSize[axis.tickSize[1]];var span=axis.max-axis.min;var suffix=opts.twelveHourClock?" %p":"";var hourCode=opts.twelveHourClock?"%I":"%H";var fmt;if(t<timeUnitSize.minute){fmt=hourCode+":%M:%S"+suffix}else if(t<timeUnitSize.day){if(span<2*timeUnitSize.day){fmt=hourCode+":%M"+suffix}else{fmt="%b %d "+hourCode+":%M"+suffix}}else if(t<timeUnitSize.month){fmt="%b %d"}else if(useQuarters&&t<timeUnitSize.quarter||!useQuarters&&t<timeUnitSize.year){if(span<timeUnitSize.year){fmt="%b"}else{fmt="%b %Y"}}else if(useQuarters&&t<timeUnitSize.year){if(span<timeUnitSize.year){fmt="Q%q"}else{fmt="Q%q %Y"}}else{fmt="%Y"}var rt=formatDate(d,fmt,opts.monthNames,opts.dayNames);return rt}}})})}$.plot.plugins.push({init:init,options:options,name:"time",version:"1.0"});$.plot.formatDate=formatDate;$.plot.dateGenerator=dateGenerator})(jQuery);
\ No newline at end of file
+!function(e){"use strict";function t(e,t,i,o){if("function"==typeof e.strftime)return e.strftime(t);var n=function(e,t){return e=""+e,t=""+(null==t?"0":t),1==e.length?t+e:e},s=[],r=!1,a=e.getHours(),c=a<12;i||(i=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]),o||(o=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]);var m;m=a>12?a-12:0==a?12:a;for(var u=-1,d=0;d<t.length;++d){var l=t.charAt(d);if(!isNaN(Number(l))&&Number(l)>0)u=Number(l);else if(r){switch(l){case"a":l=""+o[e.getDay()];break;case"b":l=""+i[e.getMonth()];break;case"d":l=n(e.getDate());break;case"e":l=n(e.getDate()," ");break;case"h":case"H":l=n(a);break;case"I":l=n(m);break;case"l":l=n(m," ");break;case"m":l=n(e.getMonth()+1);break;case"M":l=n(e.getMinutes());break;case"q":l=""+(Math.floor(e.getMonth()/3)+1);break;case"S":l=n(e.getSeconds());break;case"s":l=""+function(e,t){if(t<6&&t>0){var i=parseFloat("1e"+(t-6));e=("00000"+(e=Math.round(Math.round(e*i)/i))).slice(-6,-(6-t))}else e=("00000"+(e=Math.round(e))).slice(-6);return e}(e.getMicroseconds(),u);break;case"y":l=n(e.getFullYear()%100);break;case"Y":l=""+e.getFullYear();break;case"p":l=c?"am":"pm";break;case"P":l=c?"AM":"PM";break;case"w":l=""+e.getDay()}s.push(l),r=!1}else"%"==l?r=!0:s.push(l)}return s.join("")}function i(e){function t(e,t,i,o){e[t]=function(){return i[o].apply(i,arguments)}}var i={date:e};void 0!==e.strftime&&t(i,"strftime",e,"strftime"),t(i,"getTime",e,"getTime"),t(i,"setTime",e,"setTime");for(var o=["Date","Day","FullYear","Hours","Minutes","Month","Seconds","Milliseconds","Microseconds"],n=0;n<o.length;n++)t(i,"get"+o[n],e,"getUTC"+o[n]),t(i,"set"+o[n],e,"setUTC"+o[n]);return i}function o(e,t){if(t&&"seconds"===t.timeBase?e*=1e3:"microseconds"===t.timeBase&&(e/=1e3),e>864e13?e=864e13:e<-864e13&&(e=-864e13),"browser"===t.timezone)return r(Date,e);if(t.timezone&&"utc"!==t.timezone){if("undefined"!=typeof timezoneJS&&void 0!==timezoneJS.Date){var o=r(timezoneJS.Date,e);return o.setTimezone(t.timezone),o.setTime(e),o}return i(r(Date,e))}return i(r(Date,e))}function n(e){var t,i=e.options,n=[],r=o(e.min,i),u=0,h=i.tickSize&&"quarter"===i.tickSize[1]||i.minTickSize&&"quarter"===i.minTickSize[1]?l:d;t="seconds"===i.timeBase?a:"microseconds"===i.timeBase?m:c,null!==i.minTickSize&&void 0!==i.minTickSize&&(u="number"==typeof i.tickSize?i.tickSize:i.minTickSize[0]*t[i.minTickSize[1]]);for(var M=0;M<h.length-1&&!(e.delta<(h[M][0]*t[h[M][1]]+h[M+1][0]*t[h[M+1][1]])/2&&h[M][0]*t[h[M][1]]>=u);++M);var f=h[M][0],g=h[M][1];if("year"===g){if(null!==i.minTickSize&&void 0!==i.minTickSize&&"year"===i.minTickSize[1])f=Math.floor(i.minTickSize[0]);else{var k=parseFloat("1e"+Math.floor(Math.log(e.delta/t.year)/Math.LN10)),p=e.delta/t.year/k;f=p<1.5?1:p<3?2:p<7.5?5:10,f*=k}f<1&&(f=1)}e.tickSize=i.tickSize||[f,g];var y=e.tickSize[0],S=y*t[g=e.tickSize[1]];"microsecond"===g?r.setMicroseconds(s(r.getMicroseconds(),y)):"millisecond"===g?r.setMilliseconds(s(r.getMilliseconds(),y)):"second"===g?r.setSeconds(s(r.getSeconds(),y)):"minute"===g?r.setMinutes(s(r.getMinutes(),y)):"hour"===g?r.setHours(s(r.getHours(),y)):"month"===g?r.setMonth(s(r.getMonth(),y)):"quarter"===g?r.setMonth(3*s(r.getMonth()/3,y)):"year"===g&&r.setFullYear(s(r.getFullYear(),y)),S>=t.millisecond&&(S>=t.second?r.setMicroseconds(0):r.setMicroseconds(1e3*r.getMilliseconds())),S>=t.minute&&r.setSeconds(0),S>=t.hour&&r.setMinutes(0),S>=t.day&&r.setHours(0),S>=4*t.day&&r.setDate(1),S>=2*t.month&&r.setMonth(s(r.getMonth(),3)),S>=2*t.quarter&&r.setMonth(s(r.getMonth(),6)),S>=t.year&&r.setMonth(0);var T,v,z=0,b=Number.NaN;do{if(v=b,T=r.getTime(),b=i&&"seconds"===i.timeBase?T/1e3:i&&"microseconds"===i.timeBase?1e3*T:T,n.push(b),"month"===g||"quarter"===g)if(y<1){r.setDate(1);var q=r.getTime();r.setMonth(r.getMonth()+("quarter"===g?3:1));var B=r.getTime();r.setTime(b+z*t.hour+(B-q)*y),z=r.getHours(),r.setHours(0)}else r.setMonth(r.getMonth()+y*("quarter"===g?3:1));else"year"===g?r.setFullYear(r.getFullYear()+y):"seconds"===i.timeBase?r.setTime(1e3*(b+S)):"microseconds"===i.timeBase?r.setTime((b+S)/1e3):r.setTime(b+S)}while(b<e.max&&b!==v);return n}var s=e.plot.saturated.floorInBase,r=function(e,t){var i=new e(t),o=i.setTime.bind(i);i.update=function(e){o(e);var t=(e=Math.round(1e3*e)/1e3)/1e3;this.microseconds=1e6*(t-Math.floor(t))};var n=i.getTime.bind(i);return i.getTime=function(){return n()+this.microseconds/1e3},i.setTime=function(e){this.update(e)},i.getMicroseconds=function(){return this.microseconds},i.setMicroseconds=function(e){var t=n()+e/1e3;this.update(t)},i.setUTCMicroseconds=function(e){this.setMicroseconds(e)},i.getUTCMicroseconds=function(){return this.getMicroseconds()},i.microseconds=null,i.microEpoch=null,i.update(t),i},a={microsecond:1e-6,millisecond:.001,second:1,minute:60,hour:3600,day:86400,month:2592e3,quarter:7776e3,year:525949.2*60},c={microsecond:.001,millisecond:1,second:1e3,minute:6e4,hour:36e5,day:864e5,month:2592e6,quarter:7776e6,year:525949.2*60*1e3},m={microsecond:1,millisecond:1e3,second:1e6,minute:6e7,hour:36e8,day:864e8,month:2592e9,quarter:7776e9,year:525949.2*60*1e6},u=[[1,"microsecond"],[2,"microsecond"],[5,"microsecond"],[10,"microsecond"],[25,"microsecond"],[50,"microsecond"],[100,"microsecond"],[250,"microsecond"],[500,"microsecond"],[1,"millisecond"],[2,"millisecond"],[5,"millisecond"],[10,"millisecond"],[25,"millisecond"],[50,"millisecond"],[100,"millisecond"],[250,"millisecond"],[500,"millisecond"],[1,"second"],[2,"second"],[5,"second"],[10,"second"],[30,"second"],[1,"minute"],[2,"minute"],[5,"minute"],[10,"minute"],[30,"minute"],[1,"hour"],[2,"hour"],[4,"hour"],[8,"hour"],[12,"hour"],[1,"day"],[2,"day"],[3,"day"],[.25,"month"],[.5,"month"],[1,"month"],[2,"month"]],d=u.concat([[3,"month"],[6,"month"],[1,"year"]]),l=u.concat([[1,"quarter"],[2,"quarter"],[1,"year"]]);e.plot.plugins.push({init:function(i){i.hooks.processOptions.push(function(i){e.each(i.getAxes(),function(e,i){var s=i.options;"time"===s.mode&&(i.tickGenerator=n,i.tickFormatter=function(e,i){var n=o(e,i.options);if(null!=s.timeformat)return t(n,s.timeformat,s.monthNames,s.dayNames);var r,u=i.options.tickSize&&"quarter"==i.options.tickSize[1]||i.options.minTickSize&&"quarter"==i.options.minTickSize[1];r="seconds"===s.timeBase?a:"microseconds"===s.timeBase?m:c;var d,l,h=i.tickSize[0]*r[i.tickSize[1]],M=i.max-i.min,f=s.twelveHourClock?" %p":"",g=s.twelveHourClock?"%I":"%H";if(d="seconds"===s.timeBase?1:"microseconds"===s.timeBase?1e6:1e3,h<r.second){var k=-Math.floor(Math.log10(h/d));String(h).indexOf("25")>-1&&k++,l="%S.%"+k+"s"}else l=h<r.minute?g+":%M:%S"+f:h<r.day?M<2*r.day?g+":%M"+f:"%b %d "+g+":%M"+f:h<r.month?"%b %d":u&&h<r.quarter||!u&&h<r.year?M<r.year?"%b":"%b %Y":u&&h<r.year?M<r.year?"Q%q":"Q%q %Y":"%Y";return t(n,l,s.monthNames,s.dayNames)})})})},options:{xaxis:{timezone:null,timeformat:null,twelveHourClock:!1,monthNames:null,timeBase:"seconds"},yaxis:{timeBase:"seconds"}},name:"time",version:"1.0"}),e.plot.formatDate=t,e.plot.dateGenerator=o,e.plot.dateTickGenerator=n,e.plot.makeUtcWrapper=i}(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.touch.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,320 @@
+
+/* global jQuery */
+
+(function($) {
+    'use strict';
+
+    var options = {
+        propagateSupportedGesture: false
+    };
+
+    function init(plot) {
+        plot.hooks.processOptions.push(initTouchNavigation);
+    }
+
+    function initTouchNavigation(plot, options) {
+        var gestureState = {
+                twoTouches: false,
+                currentTapStart: { x: 0, y: 0 },
+                currentTapEnd: { x: 0, y: 0 },
+                prevTap: { x: 0, y: 0 },
+                currentTap: { x: 0, y: 0 },
+                interceptedLongTap: false,
+                isUnsupportedGesture: false,
+                prevTapTime: null,
+                tapStartTime: null,
+                longTapTriggerId: null
+            },
+            maxDistanceBetweenTaps = 20,
+            maxIntervalBetweenTaps = 500,
+            maxLongTapDistance = 20,
+            minLongTapDuration = 1500,
+            pressedTapDuration = 125,
+            mainEventHolder;
+
+        function interpretGestures(e) {
+            var o = plot.getOptions();
+
+            if (!o.pan.active && !o.zoom.active) {
+                return;
+            }
+
+            updateOnMultipleTouches(e);
+            mainEventHolder.dispatchEvent(new CustomEvent('touchevent', { detail: e }));
+
+            if (isPinchEvent(e)) {
+                executeAction(e, 'pinch');
+            } else {
+                executeAction(e, 'pan');
+                if (!wasPinchEvent(e)) {
+                    if (isDoubleTap(e)) {
+                        executeAction(e, 'doubleTap');
+                    }
+                    executeAction(e, 'tap');
+                    executeAction(e, 'longTap');
+                }
+            }
+        }
+
+        function executeAction(e, gesture) {
+            switch (gesture) {
+                case 'pan':
+                    pan[e.type](e);
+                    break;
+                case 'pinch':
+                    pinch[e.type](e);
+                    break;
+                case 'doubleTap':
+                    doubleTap.onDoubleTap(e);
+                    break;
+                case 'longTap':
+                    longTap[e.type](e);
+                    break;
+                case 'tap':
+                    tap[e.type](e);
+                    break;
+            }
+        }
+
+        function bindEvents(plot, eventHolder) {
+            mainEventHolder = eventHolder[0];
+            eventHolder[0].addEventListener('touchstart', interpretGestures, false);
+            eventHolder[0].addEventListener('touchmove', interpretGestures, false);
+            eventHolder[0].addEventListener('touchend', interpretGestures, false);
+        }
+
+        function shutdown(plot, eventHolder) {
+            eventHolder[0].removeEventListener('touchstart', interpretGestures);
+            eventHolder[0].removeEventListener('touchmove', interpretGestures);
+            eventHolder[0].removeEventListener('touchend', interpretGestures);
+            if (gestureState.longTapTriggerId) {
+                clearTimeout(gestureState.longTapTriggerId);
+                gestureState.longTapTriggerId = null;
+            }
+        }
+
+        var pan = {
+            touchstart: function(e) {
+                updatePrevForDoubleTap();
+                updateCurrentForDoubleTap(e);
+                updateStateForLongTapStart(e);
+
+                mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
+            },
+
+            touchmove: function(e) {
+                preventEventBehaviors(e);
+
+                updateCurrentForDoubleTap(e);
+                updateStateForLongTapEnd(e);
+
+                if (!gestureState.isUnsupportedGesture) {
+                    mainEventHolder.dispatchEvent(new CustomEvent('pandrag', { detail: e }));
+                }
+            },
+
+            touchend: function(e) {
+                preventEventBehaviors(e);
+
+                if (wasPinchEvent(e)) {
+                    mainEventHolder.dispatchEvent(new CustomEvent('pinchend', { detail: e }));
+                    mainEventHolder.dispatchEvent(new CustomEvent('panstart', { detail: e }));
+                } else if (noTouchActive(e)) {
+                    mainEventHolder.dispatchEvent(new CustomEvent('panend', { detail: e }));
+                }
+            }
+        };
+
+        var pinch = {
+            touchstart: function(e) {
+                mainEventHolder.dispatchEvent(new CustomEvent('pinchstart', { detail: e }));
+            },
+
+            touchmove: function(e) {
+                preventEventBehaviors(e);
+                gestureState.twoTouches = isPinchEvent(e);
+                if (!gestureState.isUnsupportedGesture) {
+                    mainEventHolder.dispatchEvent(new CustomEvent('pinchdrag', { detail: e }));
+                }
+            },
+
+            touchend: function(e) {
+                preventEventBehaviors(e);
+            }
+        };
+
+        var doubleTap = {
+            onDoubleTap: function(e) {
+                preventEventBehaviors(e);
+                mainEventHolder.dispatchEvent(new CustomEvent('doubletap', { detail: e }));
+            }
+        };
+
+        var longTap = {
+            touchstart: function(e) {
+                longTap.waitForLongTap(e);
+            },
+
+            touchmove: function(e) {
+            },
+
+            touchend: function(e) {
+                if (gestureState.longTapTriggerId) {
+                    clearTimeout(gestureState.longTapTriggerId);
+                    gestureState.longTapTriggerId = null;
+                }
+            },
+
+            isLongTap: function(e) {
+                var currentTime = new Date().getTime(),
+                    tapDuration = currentTime - gestureState.tapStartTime;
+                if (tapDuration >= minLongTapDuration && !gestureState.interceptedLongTap) {
+                    if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
+                        gestureState.interceptedLongTap = true;
+                        return true;
+                    }
+                }
+                return false;
+            },
+
+            waitForLongTap: function(e) {
+                var longTapTrigger = function() {
+                    if (longTap.isLongTap(e)) {
+                        mainEventHolder.dispatchEvent(new CustomEvent('longtap', { detail: e }));
+                    }
+                    gestureState.longTapTriggerId = null;
+                };
+                if (!gestureState.longTapTriggerId) {
+                    gestureState.longTapTriggerId = setTimeout(longTapTrigger, minLongTapDuration);
+                }
+            }
+        };
+
+        var tap = {
+            touchstart: function(e) {
+                gestureState.tapStartTime = new Date().getTime();
+            },
+
+            touchmove: function(e) {
+            },
+
+            touchend: function(e) {
+                if (tap.isTap(e)) {
+                    mainEventHolder.dispatchEvent(new CustomEvent('tap', { detail: e }));
+                    preventEventBehaviors(e);
+                }
+            },
+
+            isTap: function(e) {
+                var currentTime = new Date().getTime(),
+                    tapDuration = currentTime - gestureState.tapStartTime;
+                if (tapDuration <= pressedTapDuration) {
+                    if (distance(gestureState.currentTapStart.x, gestureState.currentTapStart.y, gestureState.currentTapEnd.x, gestureState.currentTapEnd.y) < maxLongTapDistance) {
+                        return true;
+                    }
+                }
+                return false;
+            }
+        };
+
+        if (options.pan.enableTouch === true || options.zoom.enableTouch) {
+            plot.hooks.bindEvents.push(bindEvents);
+            plot.hooks.shutdown.push(shutdown);
+        };
+
+        function updatePrevForDoubleTap() {
+            gestureState.prevTap = {
+                x: gestureState.currentTap.x,
+                y: gestureState.currentTap.y
+            };
+        };
+
+        function updateCurrentForDoubleTap(e) {
+            gestureState.currentTap = {
+                x: e.touches[0].pageX,
+                y: e.touches[0].pageY
+            };
+        }
+
+        function updateStateForLongTapStart(e) {
+            gestureState.tapStartTime = new Date().getTime();
+            gestureState.interceptedLongTap = false;
+            gestureState.currentTapStart = {
+                x: e.touches[0].pageX,
+                y: e.touches[0].pageY
+            };
+            gestureState.currentTapEnd = {
+                x: e.touches[0].pageX,
+                y: e.touches[0].pageY
+            };
+        };
+
+        function updateStateForLongTapEnd(e) {
+            gestureState.currentTapEnd = {
+                x: e.touches[0].pageX,
+                y: e.touches[0].pageY
+            };
+        };
+
+        function isDoubleTap(e) {
+            var currentTime = new Date().getTime(),
+                intervalBetweenTaps = currentTime - gestureState.prevTapTime;
+
+            if (intervalBetweenTaps >= 0 && intervalBetweenTaps < maxIntervalBetweenTaps) {
+                if (distance(gestureState.prevTap.x, gestureState.prevTap.y, gestureState.currentTap.x, gestureState.currentTap.y) < maxDistanceBetweenTaps) {
+                    e.firstTouch = gestureState.prevTap;
+                    e.secondTouch = gestureState.currentTap;
+                    return true;
+                }
+            }
+            gestureState.prevTapTime = currentTime;
+            return false;
+        }
+
+        function preventEventBehaviors(e) {
+            if (!gestureState.isUnsupportedGesture) {
+                e.preventDefault();
+                if (!plot.getOptions().propagateSupportedGesture) {
+                    e.stopPropagation();
+                }
+            }
+        }
+
+        function distance(x1, y1, x2, y2) {
+            return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
+        }
+
+        function noTouchActive(e) {
+            return (e.touches && e.touches.length === 0);
+        }
+
+        function wasPinchEvent(e) {
+            return (gestureState.twoTouches && e.touches.length === 1);
+        }
+
+        function updateOnMultipleTouches(e) {
+            if (e.touches.length >= 3) {
+                gestureState.isUnsupportedGesture = true;
+            } else {
+                gestureState.isUnsupportedGesture = false;
+            }
+        }
+
+        function isPinchEvent(e) {
+            if (e.touches && e.touches.length >= 2) {
+                if (e.touches[0].target === plot.getEventHolder() &&
+                    e.touches[1].target === plot.getEventHolder()) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: 'navigateTouch',
+        version: '0.3'
+    });
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.touch.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(t){"use strict";function e(t,e){function n(e){var n=t.getOptions();(n.pan.active||n.zoom.active)&&(h(e),v.dispatchEvent(new CustomEvent("touchevent",{detail:e})),g(e)?o(e,"pinch"):(o(e,"pan"),d(e)||(c(e)&&o(e,"doubleTap"),o(e,"tap"),o(e,"longTap"))))}function o(t,e){switch(e){case"pan":f[t.type](t);break;case"pinch":w[t.type](t);break;case"doubleTap":y.onDoubleTap(t);break;case"longTap":x[t.type](t);break;case"tap":L[t.type](t)}}function a(){l.prevTap={x:l.currentTap.x,y:l.currentTap.y}}function r(t){l.currentTap={x:t.touches[0].pageX,y:t.touches[0].pageY}}function u(t){l.tapStartTime=(new Date).getTime(),l.interceptedLongTap=!1,l.currentTapStart={x:t.touches[0].pageX,y:t.touches[0].pageY},l.currentTapEnd={x:t.touches[0].pageX,y:t.touches[0].pageY}}function p(t){l.currentTapEnd={x:t.touches[0].pageX,y:t.touches[0].pageY}}function c(t){var e=(new Date).getTime(),n=e-l.prevTapTime;return n>=0&&n<E&&s(l.prevTap.x,l.prevTap.y,l.currentTap.x,l.currentTap.y)<m?(t.firstTouch=l.prevTap,t.secondTouch=l.currentTap,!0):(l.prevTapTime=e,!1)}function i(e){l.isUnsupportedGesture||(e.preventDefault(),t.getOptions().propagateSupportedGesture||e.stopPropagation())}function s(t,e,n,o){return Math.sqrt((t-n)*(t-n)+(e-o)*(e-o))}function T(t){return t.touches&&0===t.touches.length}function d(t){return l.twoTouches&&1===t.touches.length}function h(t){t.touches.length>=3?l.isUnsupportedGesture=!0:l.isUnsupportedGesture=!1}function g(e){return!!(e.touches&&e.touches.length>=2&&e.touches[0].target===t.getEventHolder()&&e.touches[1].target===t.getEventHolder())}var v,l={twoTouches:!1,currentTapStart:{x:0,y:0},currentTapEnd:{x:0,y:0},prevTap:{x:0,y:0},currentTap:{x:0,y:0},interceptedLongTap:!1,isUnsupportedGesture:!1,prevTapTime:null,tapStartTime:null,longTapTriggerId:null},m=20,E=500,f={touchstart:function(t){a(),r(t),u(t),v.dispatchEvent(new CustomEvent("panstart",{detail:t}))},touchmove:function(t){i(t),r(t),p(t),l.isUnsupportedGesture||v.dispatchEvent(new CustomEvent("pandrag",{detail:t}))},touchend:function(t){i(t),d(t)?(v.dispatchEvent(new CustomEvent("pinchend",{detail:t})),v.dispatchEvent(new CustomEvent("panstart",{detail:t}))):T(t)&&v.dispatchEvent(new CustomEvent("panend",{detail:t}))}},w={touchstart:function(t){v.dispatchEvent(new CustomEvent("pinchstart",{detail:t}))},touchmove:function(t){i(t),l.twoTouches=g(t),l.isUnsupportedGesture||v.dispatchEvent(new CustomEvent("pinchdrag",{detail:t}))},touchend:function(t){i(t)}},y={onDoubleTap:function(t){i(t),v.dispatchEvent(new CustomEvent("doubletap",{detail:t}))}},x={touchstart:function(t){x.waitForLongTap(t)},touchmove:function(t){},touchend:function(t){l.longTapTriggerId&&(clearTimeout(l.longTapTriggerId),l.longTapTriggerId=null)},isLongTap:function(t){return(new Date).getTime()-l.tapStartTime>=1500&&!l.interceptedLongTap&&s(l.currentTapStart.x,l.currentTapStart.y,l.currentTapEnd.x,l.currentTapEnd.y)<20&&(l.interceptedLongTap=!0,!0)},waitForLongTap:function(t){l.longTapTriggerId||(l.longTapTriggerId=setTimeout(function(){x.isLongTap(t)&&v.dispatchEvent(new CustomEvent("longtap",{detail:t})),l.longTapTriggerId=null},1500))}},L={touchstart:function(t){l.tapStartTime=(new Date).getTime()},touchmove:function(t){},touchend:function(t){L.isTap(t)&&(v.dispatchEvent(new CustomEvent("tap",{detail:t})),i(t))},isTap:function(t){return(new Date).getTime()-l.tapStartTime<=125&&s(l.currentTapStart.x,l.currentTapStart.y,l.currentTapEnd.x,l.currentTapEnd.y)<20}};(!0===e.pan.enableTouch||e.zoom.enableTouch)&&(t.hooks.bindEvents.push(function(t,e){v=e[0],e[0].addEventListener("touchstart",n,!1),e[0].addEventListener("touchmove",n,!1),e[0].addEventListener("touchend",n,!1)}),t.hooks.shutdown.push(function(t,e){e[0].removeEventListener("touchstart",n),e[0].removeEventListener("touchmove",n),e[0].removeEventListener("touchend",n),l.longTapTriggerId&&(clearTimeout(l.longTapTriggerId),l.longTapTriggerId=null)}))}jQuery.plot.plugins.push({init:function(t){t.hooks.processOptions.push(e)},options:{propagateSupportedGesture:!1},name:"navigateTouch",version:"0.3"})}();
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.touchNavigate.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,360 @@
+/* global jQuery */
+
+(function($) {
+    'use strict';
+
+    var options = {
+        zoom: {
+            enableTouch: false
+        },
+        pan: {
+            enableTouch: false,
+            touchMode: 'manual'
+        },
+        recenter: {
+            enableTouch: true
+        }
+    };
+
+    var ZOOM_DISTANCE_MARGIN = $.plot.uiConstants.ZOOM_DISTANCE_MARGIN;
+
+    function init(plot) {
+        plot.hooks.processOptions.push(initTouchNavigation);
+    }
+
+    function initTouchNavigation(plot, options) {
+        var gestureState = {
+                zoomEnable: false,
+                prevDistance: null,
+                prevTapTime: 0,
+                prevPanPosition: { x: 0, y: 0 },
+                prevTapPosition: { x: 0, y: 0 }
+            },
+            navigationState = {
+                prevTouchedAxis: 'none',
+                currentTouchedAxis: 'none',
+                touchedAxis: null,
+                navigationConstraint: 'unconstrained',
+                initialState: null,
+            },
+            useManualPan = options.pan.interactive && options.pan.touchMode === 'manual',
+            smartPanLock = options.pan.touchMode === 'smartLock',
+            useSmartPan = options.pan.interactive && (smartPanLock || options.pan.touchMode === 'smart'),
+            pan, pinch, doubleTap;
+
+        function bindEvents(plot, eventHolder) {
+            var o = plot.getOptions();
+
+            if (o.zoom.interactive && o.zoom.enableTouch) {
+                eventHolder[0].addEventListener('pinchstart', pinch.start, false);
+                eventHolder[0].addEventListener('pinchdrag', pinch.drag, false);
+                eventHolder[0].addEventListener('pinchend', pinch.end, false);
+            }
+
+            if (o.pan.interactive && o.pan.enableTouch) {
+                eventHolder[0].addEventListener('panstart', pan.start, false);
+                eventHolder[0].addEventListener('pandrag', pan.drag, false);
+                eventHolder[0].addEventListener('panend', pan.end, false);
+            }
+
+            if ((o.recenter.interactive && o.recenter.enableTouch)) {
+                eventHolder[0].addEventListener('doubletap', doubleTap.recenterPlot, false);
+            }
+        }
+
+        function shutdown(plot, eventHolder) {
+            eventHolder[0].removeEventListener('panstart', pan.start);
+            eventHolder[0].removeEventListener('pandrag', pan.drag);
+            eventHolder[0].removeEventListener('panend', pan.end);
+            eventHolder[0].removeEventListener('pinchstart', pinch.start);
+            eventHolder[0].removeEventListener('pinchdrag', pinch.drag);
+            eventHolder[0].removeEventListener('pinchend', pinch.end);
+            eventHolder[0].removeEventListener('doubletap', doubleTap.recenterPlot);
+        }
+
+        pan = {
+            start: function(e) {
+                presetNavigationState(e, 'pan', gestureState);
+                updateData(e, 'pan', gestureState, navigationState);
+
+                if (useSmartPan) {
+                    var point = getPoint(e, 'pan');
+                    navigationState.initialState = plot.navigationState(point.x, point.y);
+                }
+            },
+
+            drag: function(e) {
+                presetNavigationState(e, 'pan', gestureState);
+
+                if (useSmartPan) {
+                    var point = getPoint(e, 'pan');
+                    plot.smartPan({
+                        x: navigationState.initialState.startPageX - point.x,
+                        y: navigationState.initialState.startPageY - point.y
+                    }, navigationState.initialState, navigationState.touchedAxis, false, smartPanLock);
+                } else if (useManualPan) {
+                    plot.pan({
+                        left: -delta(e, 'pan', gestureState).x,
+                        top: -delta(e, 'pan', gestureState).y,
+                        axes: navigationState.touchedAxis
+                    });
+                    updatePrevPanPosition(e, 'pan', gestureState, navigationState);
+                }
+            },
+
+            end: function(e) {
+                presetNavigationState(e, 'pan', gestureState);
+
+                if (useSmartPan) {
+                    plot.smartPan.end();
+                }
+
+                if (wasPinchEvent(e, gestureState)) {
+                    updateprevPanPosition(e, 'pan', gestureState, navigationState);
+                }
+            }
+        };
+
+        var pinchDragTimeout;
+        pinch = {
+            start: function(e) {
+                if (pinchDragTimeout) {
+                    clearTimeout(pinchDragTimeout);
+                    pinchDragTimeout = null;
+                }
+                presetNavigationState(e, 'pinch', gestureState);
+                setPrevDistance(e, gestureState);
+                updateData(e, 'pinch', gestureState, navigationState);
+            },
+
+            drag: function(e) {
+                if (pinchDragTimeout) {
+                    return;
+                }
+                pinchDragTimeout = setTimeout(function() {
+                    presetNavigationState(e, 'pinch', gestureState);
+                    plot.pan({
+                        left: -delta(e, 'pinch', gestureState).x,
+                        top: -delta(e, 'pinch', gestureState).y,
+                        axes: navigationState.touchedAxis
+                    });
+                    updatePrevPanPosition(e, 'pinch', gestureState, navigationState);
+
+                    var dist = pinchDistance(e);
+
+                    if (gestureState.zoomEnable || Math.abs(dist - gestureState.prevDistance) > ZOOM_DISTANCE_MARGIN) {
+                        zoomPlot(plot, e, gestureState, navigationState);
+
+                        //activate zoom mode
+                        gestureState.zoomEnable = true;
+                    }
+                    pinchDragTimeout = null;
+                }, 1000 / 60);
+            },
+
+            end: function(e) {
+                if (pinchDragTimeout) {
+                    clearTimeout(pinchDragTimeout);
+                    pinchDragTimeout = null;
+                }
+                presetNavigationState(e, 'pinch', gestureState);
+                gestureState.prevDistance = null;
+            }
+        };
+
+        doubleTap = {
+            recenterPlot: function(e) {
+                if (e && e.detail && e.detail.type === 'touchstart') {
+                    // only do not recenter for touch start;
+                    recenterPlotOnDoubleTap(plot, e, gestureState, navigationState);
+                }
+            }
+        };
+
+        if (options.pan.enableTouch === true || options.zoom.enableTouch === true) {
+            plot.hooks.bindEvents.push(bindEvents);
+            plot.hooks.shutdown.push(shutdown);
+        }
+
+        function presetNavigationState(e, gesture, gestureState) {
+            navigationState.touchedAxis = getAxis(plot, e, gesture, navigationState);
+            if (noAxisTouched(navigationState)) {
+                navigationState.navigationConstraint = 'unconstrained';
+            } else {
+                navigationState.navigationConstraint = 'axisConstrained';
+            }
+        }
+    }
+
+    $.plot.plugins.push({
+        init: init,
+        options: options,
+        name: 'navigateTouch',
+        version: '0.3'
+    });
+
+    function recenterPlotOnDoubleTap(plot, e, gestureState, navigationState) {
+        checkAxesForDoubleTap(plot, e, navigationState);
+        if ((navigationState.currentTouchedAxis === 'x' && navigationState.prevTouchedAxis === 'x') ||
+            (navigationState.currentTouchedAxis === 'y' && navigationState.prevTouchedAxis === 'y') ||
+            (navigationState.currentTouchedAxis === 'none' && navigationState.prevTouchedAxis === 'none')) {
+            var event;
+
+            plot.recenter({ axes: navigationState.touchedAxis });
+
+            if (navigationState.touchedAxis) {
+                event = new $.Event('re-center', { detail: { axisTouched: navigationState.touchedAxis } });
+            } else {
+                event = new $.Event('re-center', { detail: e });
+            }
+            plot.getPlaceholder().trigger(event);
+        }
+    }
+
+    function checkAxesForDoubleTap(plot, e, navigationState) {
+        var axis = plot.getTouchedAxis(e.detail.firstTouch.x, e.detail.firstTouch.y);
+        if (axis[0] !== undefined) {
+            navigationState.prevTouchedAxis = axis[0].direction;
+        }
+
+        axis = plot.getTouchedAxis(e.detail.secondTouch.x, e.detail.secondTouch.y);
+        if (axis[0] !== undefined) {
+            navigationState.touchedAxis = axis;
+            navigationState.currentTouchedAxis = axis[0].direction;
+        }
+
+        if (noAxisTouched(navigationState)) {
+            navigationState.touchedAxis = null;
+            navigationState.prevTouchedAxis = 'none';
+            navigationState.currentTouchedAxis = 'none';
+        }
+    }
+
+    function zoomPlot(plot, e, gestureState, navigationState) {
+        var offset = plot.offset(),
+            center = {
+                left: 0,
+                top: 0
+            },
+            zoomAmount = pinchDistance(e) / gestureState.prevDistance,
+            dist = pinchDistance(e);
+
+        center.left = getPoint(e, 'pinch').x - offset.left;
+        center.top = getPoint(e, 'pinch').y - offset.top;
+
+        // send the computed touched axis to the zoom function so that it only zooms on that one
+        plot.zoom({
+            center: center,
+            amount: zoomAmount,
+            axes: navigationState.touchedAxis
+        });
+        gestureState.prevDistance = dist;
+    }
+
+    function wasPinchEvent(e, gestureState) {
+        return (gestureState.zoomEnable && e.detail.touches.length === 1);
+    }
+
+    function getAxis(plot, e, gesture, navigationState) {
+        if (e.type === 'pinchstart') {
+            var axisTouch1 = plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);
+            var axisTouch2 = plot.getTouchedAxis(e.detail.touches[1].pageX, e.detail.touches[1].pageY);
+
+            if (axisTouch1.length === axisTouch2.length && axisTouch1.toString() === axisTouch2.toString()) {
+                return axisTouch1;
+            }
+        } else if (e.type === 'panstart') {
+            return plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);
+        } else if (e.type === 'pinchend') {
+            //update axis since instead on pinch, a pan event is made
+            return plot.getTouchedAxis(e.detail.touches[0].pageX, e.detail.touches[0].pageY);
+        } else {
+            return navigationState.touchedAxis;
+        }
+    }
+
+    function noAxisTouched(navigationState) {
+        return (!navigationState.touchedAxis || navigationState.touchedAxis.length === 0);
+    }
+
+    function setPrevDistance(e, gestureState) {
+        gestureState.prevDistance = pinchDistance(e);
+    }
+
+    function updateData(e, gesture, gestureState, navigationState) {
+        var axisDir,
+            point = getPoint(e, gesture);
+
+        switch (navigationState.navigationConstraint) {
+            case 'unconstrained':
+                navigationState.touchedAxis = null;
+                gestureState.prevTapPosition = {
+                    x: gestureState.prevPanPosition.x,
+                    y: gestureState.prevPanPosition.y
+                };
+                gestureState.prevPanPosition = {
+                    x: point.x,
+                    y: point.y
+                };
+                break;
+            case 'axisConstrained':
+                axisDir = navigationState.touchedAxis[0].direction;
+                navigationState.currentTouchedAxis = axisDir;
+                gestureState.prevTapPosition[axisDir] = gestureState.prevPanPosition[axisDir];
+                gestureState.prevPanPosition[axisDir] = point[axisDir];
+                break;
+            default:
+                break;
+        }
+    }
+
+    function distance(x1, y1, x2, y2) {
+        return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
+    }
+
+    function pinchDistance(e) {
+        var t1 = e.detail.touches[0],
+            t2 = e.detail.touches[1];
+        return distance(t1.pageX, t1.pageY, t2.pageX, t2.pageY);
+    }
+
+    function updatePrevPanPosition(e, gesture, gestureState, navigationState) {
+        var point = getPoint(e, gesture);
+
+        switch (navigationState.navigationConstraint) {
+            case 'unconstrained':
+                gestureState.prevPanPosition.x = point.x;
+                gestureState.prevPanPosition.y = point.y;
+                break;
+            case 'axisConstrained':
+                gestureState.prevPanPosition[navigationState.currentTouchedAxis] =
+                point[navigationState.currentTouchedAxis];
+                break;
+            default:
+                break;
+        }
+    }
+
+    function delta(e, gesture, gestureState) {
+        var point = getPoint(e, gesture);
+
+        return {
+            x: point.x - gestureState.prevPanPosition.x,
+            y: point.y - gestureState.prevPanPosition.y
+        }
+    }
+
+    function getPoint(e, gesture) {
+        if (gesture === 'pinch') {
+            return {
+                x: (e.detail.touches[0].pageX + e.detail.touches[1].pageX) / 2,
+                y: (e.detail.touches[0].pageY + e.detail.touches[1].pageY) / 2
+            }
+        } else {
+            return {
+                x: e.detail.touches[0].pageX,
+                y: e.detail.touches[0].pageY
+            }
+        }
+    }
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.touchNavigate.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(e){"use strict";function t(e,t){function i(t,n,i){A.touchedAxis=r(e,t,n,A),c(A)?A.navigationConstraint="unconstrained":A.navigationConstraint="axisConstrained"}var d,g,T,f={zoomEnable:!1,prevDistance:null,prevTapTime:0,prevPanPosition:{x:0,y:0},prevTapPosition:{x:0,y:0}},A={prevTouchedAxis:"none",currentTouchedAxis:"none",touchedAxis:null,navigationConstraint:"unconstrained",initialState:null},P=t.pan.interactive&&"manual"===t.pan.touchMode,m="smartLock"===t.pan.touchMode,y=t.pan.interactive&&(m||"smart"===t.pan.touchMode);d={start:function(t){if(i(t,"pan"),u(t,"pan",f,A),y){var n=v(t,"pan");A.initialState=e.navigationState(n.x,n.y)}},drag:function(t){if(i(t,"pan"),y){var n=v(t,"pan");e.smartPan({x:A.initialState.startPageX-n.x,y:A.initialState.startPageY-n.y},A.initialState,A.touchedAxis,!1,m)}else P&&(e.pan({left:-l(t,"pan",f).x,top:-l(t,"pan",f).y,axes:A.touchedAxis}),h(t,"pan",f,A))},end:function(t){i(t,"pan"),y&&e.smartPan.end(),o(t,f)&&updateprevPanPosition(t,"pan",f,A)}};var E;g={start:function(e){E&&(clearTimeout(E),E=null),i(e,"pinch"),s(e,f),u(e,"pinch",f,A)},drag:function(t){E||(E=setTimeout(function(){i(t,"pinch"),e.pan({left:-l(t,"pinch",f).x,top:-l(t,"pinch",f).y,axes:A.touchedAxis}),h(t,"pinch",f,A);var n=p(t);(f.zoomEnable||Math.abs(n-f.prevDistance)>x)&&(a(e,t,f,A),f.zoomEnable=!0),E=null},1e3/60))},end:function(e){E&&(clearTimeout(E),E=null),i(e,"pinch"),f.prevDistance=null}},T={recenterPlot:function(t){t&&t.detail&&"touchstart"===t.detail.type&&n(e,t,f,A)}},!0!==t.pan.enableTouch&&!0!==t.zoom.enableTouch||(e.hooks.bindEvents.push(function(e,t){var n=e.getOptions();n.zoom.interactive&&n.zoom.enableTouch&&(t[0].addEventListener("pinchstart",g.start,!1),t[0].addEventListener("pinchdrag",g.drag,!1),t[0].addEventListener("pinchend",g.end,!1)),n.pan.interactive&&n.pan.enableTouch&&(t[0].addEventListener("panstart",d.start,!1),t[0].addEventListener("pandrag",d.drag,!1),t[0].addEventListener("panend",d.end,!1)),n.recenter.interactive&&n.recenter.enableTouch&&t[0].addEventListener("doubletap",T.recenterPlot,!1)}),e.hooks.shutdown.push(function(e,t){t[0].removeEventListener("panstart",d.start),t[0].removeEventListener("pandrag",d.drag),t[0].removeEventListener("panend",d.end),t[0].removeEventListener("pinchstart",g.start),t[0].removeEventListener("pinchdrag",g.drag),t[0].removeEventListener("pinchend",g.end),t[0].removeEventListener("doubletap",T.recenterPlot)}))}function n(t,n,a,o){if(i(t,n,o),"x"===o.currentTouchedAxis&&"x"===o.prevTouchedAxis||"y"===o.currentTouchedAxis&&"y"===o.prevTouchedAxis||"none"===o.currentTouchedAxis&&"none"===o.prevTouchedAxis){var r;t.recenter({axes:o.touchedAxis}),r=o.touchedAxis?new e.Event("re-center",{detail:{axisTouched:o.touchedAxis}}):new e.Event("re-center",{detail:n}),t.getPlaceholder().trigger(r)}}function i(e,t,n){var i=e.getTouchedAxis(t.detail.firstTouch.x,t.detail.firstTouch.y);void 0!==i[0]&&(n.prevTouchedAxis=i[0].direction),void 0!==(i=e.getTouchedAxis(t.detail.secondTouch.x,t.detail.secondTouch.y))[0]&&(n.touchedAxis=i,n.currentTouchedAxis=i[0].direction),c(n)&&(n.touchedAxis=null,n.prevTouchedAxis="none",n.currentTouchedAxis="none")}function a(e,t,n,i){var a=e.offset(),o={left:0,top:0},r=p(t)/n.prevDistance,c=p(t);o.left=v(t,"pinch").x-a.left,o.top=v(t,"pinch").y-a.top,e.zoom({center:o,amount:r,axes:i.touchedAxis}),n.prevDistance=c}function o(e,t){return t.zoomEnable&&1===e.detail.touches.length}function r(e,t,n,i){if("pinchstart"!==t.type)return"panstart"===t.type?e.getTouchedAxis(t.detail.touches[0].pageX,t.detail.touches[0].pageY):"pinchend"===t.type?e.getTouchedAxis(t.detail.touches[0].pageX,t.detail.touches[0].pageY):i.touchedAxis;var a=e.getTouchedAxis(t.detail.touches[0].pageX,t.detail.touches[0].pageY),o=e.getTouchedAxis(t.detail.touches[1].pageX,t.detail.touches[1].pageY);return a.length===o.length&&a.toString()===o.toString()?a:void 0}function c(e){return!e.touchedAxis||0===e.touchedAxis.length}function s(e,t){t.prevDistance=p(e)}function u(e,t,n,i){var a,o=v(e,t);switch(i.navigationConstraint){case"unconstrained":i.touchedAxis=null,n.prevTapPosition={x:n.prevPanPosition.x,y:n.prevPanPosition.y},n.prevPanPosition={x:o.x,y:o.y};break;case"axisConstrained":a=i.touchedAxis[0].direction,i.currentTouchedAxis=a,n.prevTapPosition[a]=n.prevPanPosition[a],n.prevPanPosition[a]=o[a]}}function d(e,t,n,i){return Math.sqrt((e-n)*(e-n)+(t-i)*(t-i))}function p(e){var t=e.detail.touches[0],n=e.detail.touches[1];return d(t.pageX,t.pageY,n.pageX,n.pageY)}function h(e,t,n,i){var a=v(e,t);switch(i.navigationConstraint){case"unconstrained":n.prevPanPosition.x=a.x,n.prevPanPosition.y=a.y;break;case"axisConstrained":n.prevPanPosition[i.currentTouchedAxis]=a[i.currentTouchedAxis]}}function l(e,t,n){var i=v(e,t);return{x:i.x-n.prevPanPosition.x,y:i.y-n.prevPanPosition.y}}function v(e,t){return"pinch"===t?{x:(e.detail.touches[0].pageX+e.detail.touches[1].pageX)/2,y:(e.detail.touches[0].pageY+e.detail.touches[1].pageY)/2}:{x:e.detail.touches[0].pageX,y:e.detail.touches[0].pageY}}var x=e.plot.uiConstants.ZOOM_DISTANCE_MARGIN;e.plot.plugins.push({init:function(e){e.hooks.processOptions.push(t)},options:{zoom:{enableTouch:!1},pan:{enableTouch:!1,touchMode:"manual"},recenter:{enableTouch:!0}},name:"navigateTouch",version:"0.3"})}(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.uiConstants.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,10 @@
+(function ($) {
+    'use strict';
+    $.plot.uiConstants = {
+        SNAPPING_CONSTANT: 20,
+        PANHINT_LENGTH_CONSTANT: 10,
+        MINOR_TICKS_COUNT_CONSTANT: 4,
+        TICK_LENGTH_CONSTANT: 10,
+        ZOOM_DISTANCE_MARGIN: 25
+    };
+})(jQuery);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/pyams_skin/resources/js/ext/flot/jquery.flot.uiConstants.min.js	Wed Oct 16 13:00:43 2019 +0200
@@ -0,0 +1,1 @@
+!function(N){"use strict";jQuery.plot.uiConstants={SNAPPING_CONSTANT:20,PANHINT_LENGTH_CONSTANT:10,MINOR_TICKS_COUNT_CONSTANT:4,TICK_LENGTH_CONSTANT:10,ZOOM_DISTANCE_MARGIN:25}}();