Index: third_party/flot/jquery.flot.selection.js |
diff --git a/third_party/flot/jquery.flot.selection.js b/third_party/flot/jquery.flot.selection.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..d3c20fa4e12f258bee0be8b751d092df016d09f2 |
--- /dev/null |
+++ b/third_party/flot/jquery.flot.selection.js |
@@ -0,0 +1,360 @@ |
+/* Flot plugin for selecting regions of a plot. |
+ |
+Copyright (c) 2007-2014 IOLA and Ole Laursen. |
+Licensed under the MIT license. |
+ |
+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 |
+} |
+ |
+Selection support is enabled by setting the mode to one of "x", "y" or "xy". |
+In "x" mode, the user will only be able to specify the x range, similarly for |
+"y" mode. For "xy", the selection becomes a rectangle where both ranges can be |
+specified. "color" is color of the selection (if you need to change the color |
+later on, you can get to it with plot.getOptions().selection.color). "shape" |
+is the shape of the corners of the selection. |
+ |
+"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 |
+fact that it refers to pixels, not axis units must be taken into account. |
+Thus, for example, if there is a bar graph in time mode with BarWidth set to 1 |
+minute, setting "minSize" to 1 will not make the minimum selection size 1 |
+minute, but rather 1 pixel. Note also that setting "minSize" to 0 will prevent |
+"plotunselected" events from being fired when the user clicks the mouse without |
+dragging. |
+ |
+When selection support is enabled, a "plotselected" event will be emitted on |
+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, ... |
+ }); |
+ |
+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 |
+parameters as the "plotselected" event, in case you want to know what's |
+happening while it's happening, |
+ |
+A "plotunselected" event with no arguments is emitted when the user clicks the |
+mouse to remove the selection. As stated above, setting "minSize" to 0 will |
+destroy this behavior. |
+ |
+The plugin allso adds the following methods to the plot object: |
+ |
+- setSelection( ranges, preventEvent ) |
+ |
+ Set the selection rectangle. The passed in ranges is on the same form as |
+ returned in the "plotselected" event. If the selection mode is "x", you |
+ should put in either an xaxis range, if the mode is "y" you need to put in |
+ 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 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 |
+ true as the second parameter. If you are using multiple axes, you can |
+ specify the ranges on any of those, e.g. as x2axis/x3axis/... instead of |
+ xaxis, the plugin picks the first one it sees. |
+ |
+- clearSelection( preventEvent ) |
+ |
+ Clear the selection rectangle. Pass in true to avoid getting a |
+ "plotunselected" event. |
+ |
+- getSelection() |
+ |
+ Returns the current selection in the same format as the "plotselected" |
+ event. If there's currently no selection, the function returns null. |
+ |
+*/ |
+ |
+(function ($) { |
+ function init(plot) { |
+ var selection = { |
+ first: { x: -1, y: -1}, second: { x: -1, y: -1}, |
+ show: false, |
+ active: false |
+ }; |
+ |
+ // FIXME: The drag handling implemented here should be |
+ // abstracted out, there's some similar code from a library in |
+ // the navigation plugin, this should be massaged a bit to fit |
+ // the Flot cases here better and reused. Doing this would |
+ // make this plugin much slimmer. |
+ 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; |
+ |
+ // cancel out any text selections |
+ document.body.focus(); |
+ |
+ // prevent text selection and drag in old-school browsers |
+ 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; |
+ |
+ // 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) |
+ document.onselectstart = savedhandlers.onselectstart; |
+ if (document.ondrag !== undefined) |
+ document.ondrag = savedhandlers.ondrag; |
+ |
+ // no more dragging |
+ selection.active = false; |
+ updateSelection(e); |
+ |
+ if (selectionIsSane()) |
+ triggerSelectedEvent(); |
+ else { |
+ // this counts as a clear |
+ 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 ]); |
+ |
+ // backwards-compat stuff, to be removed in future |
+ 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 taken from markings support in Flot |
+ 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"; // support x1axis as xaxis |
+ if (ranges[key]) { |
+ from = ranges[key].from; |
+ to = ranges[key].to; |
+ break; |
+ } |
+ } |
+ } |
+ |
+ // backwards-compat stuff - to be removed in future |
+ if (!ranges[key]) { |
+ axis = coord == "x" ? plot.getXAxes()[0] : plot.getYAxes()[0]; |
+ from = ranges[coord + "1"]; |
+ to = ranges[coord + "2"]; |
+ } |
+ |
+ // auto-reverse as an added bonus |
+ 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) { |
+ // draw selection |
+ 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', 0.8).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, |
+ y = Math.min(selection.first.y, selection.second.y) + 0.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, // one of null, "x", "y" or "xy" |
+ color: "#e8cfac", |
+ shape: "round", // one of "round", "miter", or "bevel" |
+ minSize: 5 // minimum number of pixels |
+ } |
+ }, |
+ name: 'selection', |
+ version: '1.1' |
+ }); |
+})(jQuery); |