src/pyams_skin/resources/js/ext/flot/jquery.flot.selection.js
changeset 566 a1707c607eec
parent 565 318533413200
child 567 bca1726b1d85
equal deleted inserted replaced
565:318533413200 566:a1707c607eec
     1 /* Flot plugin for selecting regions of a plot.
       
     2 
       
     3 Copyright (c) 2007-2014 IOLA and Ole Laursen.
       
     4 Licensed under the MIT license.
       
     5 
       
     6 The plugin supports these options:
       
     7 
       
     8 selection: {
       
     9     mode: null or "x" or "y" or "xy" or "smart",
       
    10     color: color,
       
    11     shape: "round" or "miter" or "bevel",
       
    12     visualization: "fill" or "focus",
       
    13     minSize: number of pixels
       
    14 }
       
    15 
       
    16 Selection support is enabled by setting the mode to one of "x", "y" or "xy".
       
    17 In "x" mode, the user will only be able to specify the x range, similarly for
       
    18 "y" mode. For "xy", the selection becomes a rectangle where both ranges can be
       
    19 specified. "color" is color of the selection (if you need to change the color
       
    20 later on, you can get to it with plot.getOptions().selection.color). "shape"
       
    21 is the shape of the corners of the selection.
       
    22 
       
    23 The way how the selection is visualized, can be changed by using the option
       
    24 "visualization". Flot currently supports two modes: "focus" and "fill". The
       
    25 option "focus" draws a colored bezel around the selected area while keeping
       
    26 the selected area clear. The option "fill" highlights (i.e., fills) the
       
    27 selected area with a colored highlight.
       
    28 
       
    29 "minSize" is the minimum size a selection can be in pixels. This value can
       
    30 be customized to determine the smallest size a selection can be and still
       
    31 have the selection rectangle be displayed. When customizing this value, the
       
    32 fact that it refers to pixels, not axis units must be taken into account.
       
    33 Thus, for example, if there is a bar graph in time mode with BarWidth set to 1
       
    34 minute, setting "minSize" to 1 will not make the minimum selection size 1
       
    35 minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent
       
    36 "plotunselected" events from being fired when the user clicks the mouse without
       
    37 dragging.
       
    38 
       
    39 When selection support is enabled, a "plotselected" event will be emitted on
       
    40 the DOM element you passed into the plot function. The event handler gets a
       
    41 parameter with the ranges selected on the axes, like this:
       
    42 
       
    43     placeholder.bind( "plotselected", function( event, ranges ) {
       
    44         alert("You selected " + ranges.xaxis.from + " to " + ranges.xaxis.to)
       
    45         // similar for yaxis - with multiple axes, the extra ones are in
       
    46         // x2axis, x3axis, ...
       
    47     });
       
    48 
       
    49 The "plotselected" event is only fired when the user has finished making the
       
    50 selection. A "plotselecting" event is fired during the process with the same
       
    51 parameters as the "plotselected" event, in case you want to know what's
       
    52 happening while it's happening,
       
    53 
       
    54 A "plotunselected" event with no arguments is emitted when the user clicks the
       
    55 mouse to remove the selection. As stated above, setting "minSize" to 0 will
       
    56 destroy this behavior.
       
    57 
       
    58 The plugin allso adds the following methods to the plot object:
       
    59 
       
    60 - setSelection( ranges, preventEvent )
       
    61 
       
    62   Set the selection rectangle. The passed in ranges is on the same form as
       
    63   returned in the "plotselected" event. If the selection mode is "x", you
       
    64   should put in either an xaxis range, if the mode is "y" you need to put in
       
    65   an yaxis range and both xaxis and yaxis if the selection mode is "xy", like
       
    66   this:
       
    67 
       
    68     setSelection({ xaxis: { from: 0, to: 10 }, yaxis: { from: 40, to: 60 } });
       
    69 
       
    70   setSelection will trigger the "plotselected" event when called. If you don't
       
    71   want that to happen, e.g. if you're inside a "plotselected" handler, pass
       
    72   true as the second parameter. If you are using multiple axes, you can
       
    73   specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of
       
    74   xaxis, the plugin picks the first one it sees.
       
    75 
       
    76 - clearSelection( preventEvent )
       
    77 
       
    78   Clear the selection rectangle. Pass in true to avoid getting a
       
    79   "plotunselected" event.
       
    80 
       
    81 - getSelection()
       
    82 
       
    83   Returns the current selection in the same format as the "plotselected"
       
    84   event. If there's currently no selection, the function returns null.
       
    85 
       
    86 */
       
    87 
       
    88 (function ($) {
       
    89     function init(plot) {
       
    90         var selection = {
       
    91             first: {x: -1, y: -1},
       
    92             second: {x: -1, y: -1},
       
    93             show: false,
       
    94             currentMode: 'xy',
       
    95             active: false
       
    96         };
       
    97 
       
    98         var SNAPPING_CONSTANT = $.plot.uiConstants.SNAPPING_CONSTANT;
       
    99 
       
   100         // FIXME: The drag handling implemented here should be
       
   101         // abstracted out, there's some similar code from a library in
       
   102         // the navigation plugin, this should be massaged a bit to fit
       
   103         // the Flot cases here better and reused. Doing this would
       
   104         // make this plugin much slimmer.
       
   105         var savedhandlers = {};
       
   106 
       
   107         var mouseUpHandler = null;
       
   108 
       
   109         function onMouseMove(e) {
       
   110             if (selection.active) {
       
   111                 updateSelection(e);
       
   112 
       
   113                 plot.getPlaceholder().trigger("plotselecting", [ getSelection() ]);
       
   114             }
       
   115         }
       
   116 
       
   117         function onMouseDown(e) {
       
   118             var o = plot.getOptions();
       
   119             // only accept left-click
       
   120             if (e.which !== 1 || o.selection.mode === null) return;
       
   121 
       
   122             // reinitialize currentMode
       
   123             selection.currentMode = 'xy';
       
   124 
       
   125             // cancel out any text selections
       
   126             document.body.focus();
       
   127 
       
   128             // prevent text selection and drag in old-school browsers
       
   129             if (document.onselectstart !== undefined && savedhandlers.onselectstart == null) {
       
   130                 savedhandlers.onselectstart = document.onselectstart;
       
   131                 document.onselectstart = function () { return false; };
       
   132             }
       
   133             if (document.ondrag !== undefined && savedhandlers.ondrag == null) {
       
   134                 savedhandlers.ondrag = document.ondrag;
       
   135                 document.ondrag = function () { return false; };
       
   136             }
       
   137 
       
   138             setSelectionPos(selection.first, e);
       
   139 
       
   140             selection.active = true;
       
   141 
       
   142             // this is a bit silly, but we have to use a closure to be
       
   143             // able to whack the same handler again
       
   144             mouseUpHandler = function (e) { onMouseUp(e); };
       
   145 
       
   146             $(document).one("mouseup", mouseUpHandler);
       
   147         }
       
   148 
       
   149         function onMouseUp(e) {
       
   150             mouseUpHandler = null;
       
   151 
       
   152             // revert drag stuff for old-school browsers
       
   153             if (document.onselectstart !== undefined) {
       
   154                 document.onselectstart = savedhandlers.onselectstart;
       
   155             }
       
   156 
       
   157             if (document.ondrag !== undefined) {
       
   158                 document.ondrag = savedhandlers.ondrag;
       
   159             }
       
   160 
       
   161             // no more dragging
       
   162             selection.active = false;
       
   163             updateSelection(e);
       
   164 
       
   165             if (selectionIsSane()) {
       
   166                 triggerSelectedEvent();
       
   167             } else {
       
   168                 // this counts as a clear
       
   169                 plot.getPlaceholder().trigger("plotunselected", [ ]);
       
   170                 plot.getPlaceholder().trigger("plotselecting", [ null ]);
       
   171             }
       
   172 
       
   173             return false;
       
   174         }
       
   175 
       
   176         function getSelection() {
       
   177             if (!selectionIsSane()) return null;
       
   178 
       
   179             if (!selection.show) return null;
       
   180 
       
   181             var r = {},
       
   182                 c1 = {x: selection.first.x, y: selection.first.y},
       
   183                 c2 = {x: selection.second.x, y: selection.second.y};
       
   184 
       
   185             if (selectionDirection(plot) === 'x') {
       
   186                 c1.y = 0;
       
   187                 c2.y = plot.height();
       
   188             }
       
   189 
       
   190             if (selectionDirection(plot) === 'y') {
       
   191                 c1.x = 0;
       
   192                 c2.x = plot.width();
       
   193             }
       
   194 
       
   195             $.each(plot.getAxes(), function (name, axis) {
       
   196                 if (axis.used) {
       
   197                     var p1 = axis.c2p(c1[axis.direction]), p2 = axis.c2p(c2[axis.direction]);
       
   198                     r[name] = { from: Math.min(p1, p2), to: Math.max(p1, p2) };
       
   199                 }
       
   200             });
       
   201             return r;
       
   202         }
       
   203 
       
   204         function triggerSelectedEvent() {
       
   205             var r = getSelection();
       
   206 
       
   207             plot.getPlaceholder().trigger("plotselected", [ r ]);
       
   208 
       
   209             // backwards-compat stuff, to be removed in future
       
   210             if (r.xaxis && r.yaxis) {
       
   211                 plot.getPlaceholder().trigger("selected", [ { x1: r.xaxis.from, y1: r.yaxis.from, x2: r.xaxis.to, y2: r.yaxis.to } ]);
       
   212             }
       
   213         }
       
   214 
       
   215         function clamp(min, value, max) {
       
   216             return value < min ? min : (value > max ? max : value);
       
   217         }
       
   218 
       
   219         function selectionDirection(plot) {
       
   220             var o = plot.getOptions();
       
   221 
       
   222             if (o.selection.mode === 'smart') {
       
   223                 return selection.currentMode;
       
   224             } else {
       
   225                 return o.selection.mode;
       
   226             }
       
   227         }
       
   228 
       
   229         function updateMode(pos) {
       
   230             if (selection.first) {
       
   231                 var delta = {
       
   232                     x: pos.x - selection.first.x,
       
   233                     y: pos.y - selection.first.y
       
   234                 };
       
   235 
       
   236                 if (Math.abs(delta.x) < SNAPPING_CONSTANT) {
       
   237                     selection.currentMode = 'y';
       
   238                 } else if (Math.abs(delta.y) < SNAPPING_CONSTANT) {
       
   239                     selection.currentMode = 'x';
       
   240                 } else {
       
   241                     selection.currentMode = 'xy';
       
   242                 }
       
   243             }
       
   244         }
       
   245 
       
   246         function setSelectionPos(pos, e) {
       
   247             var offset = plot.getPlaceholder().offset();
       
   248             var plotOffset = plot.getPlotOffset();
       
   249             pos.x = clamp(0, e.pageX - offset.left - plotOffset.left, plot.width());
       
   250             pos.y = clamp(0, e.pageY - offset.top - plotOffset.top, plot.height());
       
   251 
       
   252             if (pos !== selection.first) updateMode(pos);
       
   253 
       
   254             if (selectionDirection(plot) === "y") {
       
   255                 pos.x = pos === selection.first ? 0 : plot.width();
       
   256             }
       
   257 
       
   258             if (selectionDirection(plot) === "x") {
       
   259                 pos.y = pos === selection.first ? 0 : plot.height();
       
   260             }
       
   261         }
       
   262 
       
   263         function updateSelection(pos) {
       
   264             if (pos.pageX == null) return;
       
   265 
       
   266             setSelectionPos(selection.second, pos);
       
   267             if (selectionIsSane()) {
       
   268                 selection.show = true;
       
   269                 plot.triggerRedrawOverlay();
       
   270             } else clearSelection(true);
       
   271         }
       
   272 
       
   273         function clearSelection(preventEvent) {
       
   274             if (selection.show) {
       
   275                 selection.show = false;
       
   276                 selection.currentMode = '';
       
   277                 plot.triggerRedrawOverlay();
       
   278                 if (!preventEvent) {
       
   279                     plot.getPlaceholder().trigger("plotunselected", [ ]);
       
   280                 }
       
   281             }
       
   282         }
       
   283 
       
   284         // function taken from markings support in Flot
       
   285         function extractRange(ranges, coord) {
       
   286             var axis, from, to, key, axes = plot.getAxes();
       
   287 
       
   288             for (var k in axes) {
       
   289                 axis = axes[k];
       
   290                 if (axis.direction === coord) {
       
   291                     key = coord + axis.n + "axis";
       
   292                     if (!ranges[key] && axis.n === 1) {
       
   293                         // support x1axis as xaxis
       
   294                         key = coord + "axis";
       
   295                     }
       
   296 
       
   297                     if (ranges[key]) {
       
   298                         from = ranges[key].from;
       
   299                         to = ranges[key].to;
       
   300                         break;
       
   301                     }
       
   302                 }
       
   303             }
       
   304 
       
   305             // backwards-compat stuff - to be removed in future
       
   306             if (!ranges[key]) {
       
   307                 axis = coord === "x" ? plot.getXAxes()[0] : plot.getYAxes()[0];
       
   308                 from = ranges[coord + "1"];
       
   309                 to = ranges[coord + "2"];
       
   310             }
       
   311 
       
   312             // auto-reverse as an added bonus
       
   313             if (from != null && to != null && from > to) {
       
   314                 var tmp = from;
       
   315                 from = to;
       
   316                 to = tmp;
       
   317             }
       
   318 
       
   319             return { from: from, to: to, axis: axis };
       
   320         }
       
   321 
       
   322         function setSelection(ranges, preventEvent) {
       
   323             var range;
       
   324 
       
   325             if (selectionDirection(plot) === "y") {
       
   326                 selection.first.x = 0;
       
   327                 selection.second.x = plot.width();
       
   328             } else {
       
   329                 range = extractRange(ranges, "x");
       
   330                 selection.first.x = range.axis.p2c(range.from);
       
   331                 selection.second.x = range.axis.p2c(range.to);
       
   332             }
       
   333 
       
   334             if (selectionDirection(plot) === "x") {
       
   335                 selection.first.y = 0;
       
   336                 selection.second.y = plot.height();
       
   337             } else {
       
   338                 range = extractRange(ranges, "y");
       
   339                 selection.first.y = range.axis.p2c(range.from);
       
   340                 selection.second.y = range.axis.p2c(range.to);
       
   341             }
       
   342 
       
   343             selection.show = true;
       
   344             plot.triggerRedrawOverlay();
       
   345             if (!preventEvent && selectionIsSane()) {
       
   346                 triggerSelectedEvent();
       
   347             }
       
   348         }
       
   349 
       
   350         function selectionIsSane() {
       
   351             var minSize = plot.getOptions().selection.minSize;
       
   352             return Math.abs(selection.second.x - selection.first.x) >= minSize &&
       
   353                 Math.abs(selection.second.y - selection.first.y) >= minSize;
       
   354         }
       
   355 
       
   356         plot.clearSelection = clearSelection;
       
   357         plot.setSelection = setSelection;
       
   358         plot.getSelection = getSelection;
       
   359 
       
   360         plot.hooks.bindEvents.push(function(plot, eventHolder) {
       
   361             var o = plot.getOptions();
       
   362             if (o.selection.mode != null) {
       
   363                 eventHolder.mousemove(onMouseMove);
       
   364                 eventHolder.mousedown(onMouseDown);
       
   365             }
       
   366         });
       
   367 
       
   368         function drawSelectionDecorations(ctx, x, y, w, h, oX, oY, mode) {
       
   369             var spacing = 3;
       
   370             var fullEarWidth = 15;
       
   371             var earWidth = Math.max(0, Math.min(fullEarWidth, w / 2 - 2, h / 2 - 2));
       
   372             ctx.fillStyle = '#ffffff';
       
   373 
       
   374             if (mode === 'xy') {
       
   375                 ctx.beginPath();
       
   376                 ctx.moveTo(x, y + earWidth);
       
   377                 ctx.lineTo(x - 3, y + earWidth);
       
   378                 ctx.lineTo(x - 3, y - 3);
       
   379                 ctx.lineTo(x + earWidth, y - 3);
       
   380                 ctx.lineTo(x + earWidth, y);
       
   381                 ctx.lineTo(x, y);
       
   382                 ctx.closePath();
       
   383 
       
   384                 ctx.moveTo(x, y + h - earWidth);
       
   385                 ctx.lineTo(x - 3, y + h - earWidth);
       
   386                 ctx.lineTo(x - 3, y + h + 3);
       
   387                 ctx.lineTo(x + earWidth, y + h + 3);
       
   388                 ctx.lineTo(x + earWidth, y + h);
       
   389                 ctx.lineTo(x, y + h);
       
   390                 ctx.closePath();
       
   391 
       
   392                 ctx.moveTo(x + w, y + earWidth);
       
   393                 ctx.lineTo(x + w + 3, y + earWidth);
       
   394                 ctx.lineTo(x + w + 3, y - 3);
       
   395                 ctx.lineTo(x + w - earWidth, y - 3);
       
   396                 ctx.lineTo(x + w - earWidth, y);
       
   397                 ctx.lineTo(x + w, y);
       
   398                 ctx.closePath();
       
   399 
       
   400                 ctx.moveTo(x + w, y + h - earWidth);
       
   401                 ctx.lineTo(x + w + 3, y + h - earWidth);
       
   402                 ctx.lineTo(x + w + 3, y + h + 3);
       
   403                 ctx.lineTo(x + w - earWidth, y + h + 3);
       
   404                 ctx.lineTo(x + w - earWidth, y + h);
       
   405                 ctx.lineTo(x + w, y + h);
       
   406                 ctx.closePath();
       
   407 
       
   408                 ctx.stroke();
       
   409                 ctx.fill();
       
   410             }
       
   411 
       
   412             x = oX;
       
   413             y = oY;
       
   414 
       
   415             if (mode === 'x') {
       
   416                 ctx.beginPath();
       
   417                 ctx.moveTo(x, y + fullEarWidth);
       
   418                 ctx.lineTo(x, y - fullEarWidth);
       
   419                 ctx.lineTo(x - spacing, y - fullEarWidth);
       
   420                 ctx.lineTo(x - spacing, y + fullEarWidth);
       
   421                 ctx.closePath();
       
   422 
       
   423                 ctx.moveTo(x + w, y + fullEarWidth);
       
   424                 ctx.lineTo(x + w, y - fullEarWidth);
       
   425                 ctx.lineTo(x + w + spacing, y - fullEarWidth);
       
   426                 ctx.lineTo(x + w + spacing, y + fullEarWidth);
       
   427                 ctx.closePath();
       
   428                 ctx.stroke();
       
   429                 ctx.fill();
       
   430             }
       
   431 
       
   432             if (mode === 'y') {
       
   433                 ctx.beginPath();
       
   434 
       
   435                 ctx.moveTo(x - fullEarWidth, y);
       
   436                 ctx.lineTo(x + fullEarWidth, y);
       
   437                 ctx.lineTo(x + fullEarWidth, y - spacing);
       
   438                 ctx.lineTo(x - fullEarWidth, y - spacing);
       
   439                 ctx.closePath();
       
   440 
       
   441                 ctx.moveTo(x - fullEarWidth, y + h);
       
   442                 ctx.lineTo(x + fullEarWidth, y + h);
       
   443                 ctx.lineTo(x + fullEarWidth, y + h + spacing);
       
   444                 ctx.lineTo(x - fullEarWidth, y + h + spacing);
       
   445                 ctx.closePath();
       
   446                 ctx.stroke();
       
   447                 ctx.fill();
       
   448             }
       
   449         }
       
   450 
       
   451         plot.hooks.drawOverlay.push(function (plot, ctx) {
       
   452             // draw selection
       
   453             if (selection.show && selectionIsSane()) {
       
   454                 var plotOffset = plot.getPlotOffset();
       
   455                 var o = plot.getOptions();
       
   456 
       
   457                 ctx.save();
       
   458                 ctx.translate(plotOffset.left, plotOffset.top);
       
   459 
       
   460                 var c = $.color.parse(o.selection.color);
       
   461                 var visualization = o.selection.visualization;
       
   462 
       
   463                 var scalingFactor = 1;
       
   464 
       
   465                 // use a dimmer scaling factor if visualization is "fill"
       
   466                 if (visualization === "fill") {
       
   467                     scalingFactor = 0.8;
       
   468                 }
       
   469 
       
   470                 ctx.strokeStyle = c.scale('a', scalingFactor).toString();
       
   471                 ctx.lineWidth = 1;
       
   472                 ctx.lineJoin = o.selection.shape;
       
   473                 ctx.fillStyle = c.scale('a', 0.4).toString();
       
   474 
       
   475                 var x = Math.min(selection.first.x, selection.second.x) + 0.5,
       
   476                     oX = x,
       
   477                     y = Math.min(selection.first.y, selection.second.y) + 0.5,
       
   478                     oY = y,
       
   479                     w = Math.abs(selection.second.x - selection.first.x) - 1,
       
   480                     h = Math.abs(selection.second.y - selection.first.y) - 1;
       
   481 
       
   482                 if (selectionDirection(plot) === 'x') {
       
   483                     h += y;
       
   484                     y = 0;
       
   485                 }
       
   486 
       
   487                 if (selectionDirection(plot) === 'y') {
       
   488                     w += x;
       
   489                     x = 0;
       
   490                 }
       
   491 
       
   492                 if (visualization === "fill") {
       
   493                     ctx.fillRect(x, y, w, h);
       
   494                     ctx.strokeRect(x, y, w, h);
       
   495                 } else {
       
   496                     ctx.fillRect(0, 0, plot.width(), plot.height());
       
   497                     ctx.clearRect(x, y, w, h);
       
   498                     drawSelectionDecorations(ctx, x, y, w, h, oX, oY, selectionDirection(plot));
       
   499                 }
       
   500 
       
   501                 ctx.restore();
       
   502             }
       
   503         });
       
   504 
       
   505         plot.hooks.shutdown.push(function (plot, eventHolder) {
       
   506             eventHolder.unbind("mousemove", onMouseMove);
       
   507             eventHolder.unbind("mousedown", onMouseDown);
       
   508 
       
   509             if (mouseUpHandler) {
       
   510                 $(document).unbind("mouseup", mouseUpHandler);
       
   511             }
       
   512         });
       
   513     }
       
   514 
       
   515     $.plot.plugins.push({
       
   516         init: init,
       
   517         options: {
       
   518             selection: {
       
   519                 mode: null, // one of null, "x", "y" or "xy"
       
   520                 visualization: "focus", // "focus" or "fill"
       
   521                 color: "#888888",
       
   522                 shape: "round", // one of "round", "miter", or "bevel"
       
   523                 minSize: 5 // minimum number of pixels
       
   524             }
       
   525         },
       
   526         name: 'selection',
       
   527         version: '1.1'
       
   528     });
       
   529 })(jQuery);