src/pyams_skin/resources/js/ext/flot/jquery.flot.selection.js
changeset 546 9f02c09d2393
parent 98 89a28618a327
--- 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
             }