|
/** |
|
* Selection Handles Plugin |
|
* |
|
* |
|
* Options |
|
* show - True enables the handles plugin. |
|
* drag - Left and Right drag handles |
|
* scroll - Scrolling handle |
|
*/ |
|
(function () { |
|
|
|
function isLeftClick (e, type) { |
|
return (e.which ? (e.which === 1) : (e.button === 0 || e.button === 1)); |
|
} |
|
|
|
function boundX(x, graph) { |
|
return Math.min(Math.max(0, x), graph.plotWidth - 1); |
|
} |
|
|
|
function boundY(y, graph) { |
|
return Math.min(Math.max(0, y), graph.plotHeight); |
|
} |
|
|
|
var |
|
D = Flotr.DOM, |
|
E = Flotr.EventAdapter, |
|
_ = Flotr._; |
|
|
|
|
|
Flotr.addPlugin('selection', { |
|
|
|
options: { |
|
pinchOnly: null, // Only select on pinch |
|
mode: null, // => one of null, 'x', 'y' or 'xy' |
|
color: '#B6D9FF', // => selection box color |
|
fps: 20 // => frames-per-second |
|
}, |
|
|
|
callbacks: { |
|
'flotr:mouseup' : function (event) { |
|
|
|
var |
|
options = this.options.selection, |
|
selection = this.selection, |
|
pointer = this.getEventPosition(event); |
|
|
|
if (!options || !options.mode) return; |
|
if (selection.interval) clearInterval(selection.interval); |
|
|
|
if (this.multitouches) { |
|
selection.updateSelection(); |
|
} else |
|
if (!options.pinchOnly) { |
|
selection.setSelectionPos(selection.selection.second, pointer); |
|
} |
|
selection.clearSelection(); |
|
|
|
if(selection.selecting && selection.selectionIsSane()){ |
|
selection.drawSelection(); |
|
selection.fireSelectEvent(); |
|
this.ignoreClick = true; |
|
} |
|
}, |
|
'flotr:mousedown' : function (event) { |
|
|
|
var |
|
options = this.options.selection, |
|
selection = this.selection, |
|
pointer = this.getEventPosition(event); |
|
|
|
if (!options || !options.mode) return; |
|
if (!options.mode || (!isLeftClick(event) && _.isUndefined(event.touches))) return; |
|
if (!options.pinchOnly) selection.setSelectionPos(selection.selection.first, pointer); |
|
if (selection.interval) clearInterval(selection.interval); |
|
|
|
this.lastMousePos.pageX = null; |
|
selection.selecting = false; |
|
selection.interval = setInterval( |
|
_.bind(selection.updateSelection, this), |
|
1000 / options.fps |
|
); |
|
}, |
|
'flotr:destroy' : function (event) { |
|
clearInterval(this.selection.interval); |
|
} |
|
}, |
|
|
|
// TODO This isn't used. Maybe it belongs in the draw area and fire select event methods? |
|
getArea: function() { |
|
|
|
var |
|
s = this.selection.selection, |
|
a = this.axes, |
|
first = s.first, |
|
second = s.second, |
|
x1, x2, y1, y2; |
|
|
|
x1 = a.x.p2d(s.first.x); |
|
x2 = a.x.p2d(s.second.x); |
|
y1 = a.y.p2d(s.first.y); |
|
y2 = a.y.p2d(s.second.y); |
|
|
|
return { |
|
x1 : Math.min(x1, x2), |
|
y1 : Math.min(y1, y2), |
|
x2 : Math.max(x1, x2), |
|
y2 : Math.max(y1, y2), |
|
xfirst : x1, |
|
xsecond : x2, |
|
yfirst : y1, |
|
ysecond : y2 |
|
}; |
|
}, |
|
|
|
selection: {first: {x: -1, y: -1}, second: {x: -1, y: -1}}, |
|
prevSelection: null, |
|
interval: null, |
|
|
|
/** |
|
* Fires the 'flotr:select' event when the user made a selection. |
|
*/ |
|
fireSelectEvent: function(name){ |
|
var |
|
area = this.selection.getArea(); |
|
name = name || 'select'; |
|
area.selection = this.selection.selection; |
|
E.fire(this.el, 'flotr:'+name, [area, this]); |
|
}, |
|
|
|
/** |
|
* Allows the user the manually select an area. |
|
* @param {Object} area - Object with coordinates to select. |
|
*/ |
|
setSelection: function(area, preventEvent){ |
|
var options = this.options, |
|
xa = this.axes.x, |
|
ya = this.axes.y, |
|
vertScale = ya.scale, |
|
hozScale = xa.scale, |
|
selX = options.selection.mode.indexOf('x') != -1, |
|
selY = options.selection.mode.indexOf('y') != -1, |
|
s = this.selection.selection; |
|
|
|
this.selection.clearSelection(); |
|
|
|
s.first.y = boundY((selX && !selY) ? 0 : (ya.max - area.y1) * vertScale, this); |
|
s.second.y = boundY((selX && !selY) ? this.plotHeight - 1: (ya.max - area.y2) * vertScale, this); |
|
s.first.x = boundX((selY && !selX) ? 0 : (area.x1 - xa.min) * hozScale, this); |
|
s.second.x = boundX((selY && !selX) ? this.plotWidth : (area.x2 - xa.min) * hozScale, this); |
|
|
|
this.selection.drawSelection(); |
|
if (!preventEvent) |
|
this.selection.fireSelectEvent(); |
|
}, |
|
|
|
/** |
|
* Calculates the position of the selection. |
|
* @param {Object} pos - Position object. |
|
* @param {Event} event - Event object. |
|
*/ |
|
setSelectionPos: function(pos, pointer) { |
|
var mode = this.options.selection.mode, |
|
selection = this.selection.selection; |
|
|
|
if(mode.indexOf('x') == -1) { |
|
pos.x = (pos == selection.first) ? 0 : this.plotWidth; |
|
}else{ |
|
pos.x = boundX(pointer.relX, this); |
|
} |
|
|
|
if (mode.indexOf('y') == -1) { |
|
pos.y = (pos == selection.first) ? 0 : this.plotHeight - 1; |
|
}else{ |
|
pos.y = boundY(pointer.relY, this); |
|
} |
|
}, |
|
/** |
|
* Draws the selection box. |
|
*/ |
|
drawSelection: function() { |
|
|
|
this.selection.fireSelectEvent('selecting'); |
|
|
|
var s = this.selection.selection, |
|
octx = this.octx, |
|
options = this.options, |
|
plotOffset = this.plotOffset, |
|
prevSelection = this.selection.prevSelection; |
|
|
|
if (prevSelection && |
|
s.first.x == prevSelection.first.x && |
|
s.first.y == prevSelection.first.y && |
|
s.second.x == prevSelection.second.x && |
|
s.second.y == prevSelection.second.y) { |
|
return; |
|
} |
|
|
|
octx.save(); |
|
octx.strokeStyle = this.processColor(options.selection.color, {opacity: 0.8}); |
|
octx.lineWidth = 1; |
|
octx.lineJoin = 'miter'; |
|
octx.fillStyle = this.processColor(options.selection.color, {opacity: 0.4}); |
|
|
|
this.selection.prevSelection = { |
|
first: { x: s.first.x, y: s.first.y }, |
|
second: { x: s.second.x, y: s.second.y } |
|
}; |
|
|
|
var x = Math.min(s.first.x, s.second.x), |
|
y = Math.min(s.first.y, s.second.y), |
|
w = Math.abs(s.second.x - s.first.x), |
|
h = Math.abs(s.second.y - s.first.y); |
|
|
|
octx.fillRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); |
|
octx.strokeRect(x + plotOffset.left+0.5, y + plotOffset.top+0.5, w, h); |
|
octx.restore(); |
|
}, |
|
|
|
/** |
|
* Updates (draws) the selection box. |
|
*/ |
|
updateSelection: function(){ |
|
if (!this.lastMousePos.pageX) return; |
|
|
|
this.selection.selecting = true; |
|
|
|
if (this.multitouches) { |
|
this.selection.setSelectionPos(this.selection.selection.first, this.getEventPosition(this.multitouches[0])); |
|
this.selection.setSelectionPos(this.selection.selection.second, this.getEventPosition(this.multitouches[1])); |
|
} else |
|
if (this.options.selection.pinchOnly) { |
|
return; |
|
} else { |
|
this.selection.setSelectionPos(this.selection.selection.second, this.lastMousePos); |
|
} |
|
|
|
this.selection.clearSelection(); |
|
|
|
if(this.selection.selectionIsSane()) { |
|
this.selection.drawSelection(); |
|
} |
|
}, |
|
|
|
/** |
|
* Removes the selection box from the overlay canvas. |
|
*/ |
|
clearSelection: function() { |
|
if (!this.selection.prevSelection) return; |
|
|
|
var prevSelection = this.selection.prevSelection, |
|
lw = 1, |
|
plotOffset = this.plotOffset, |
|
x = Math.min(prevSelection.first.x, prevSelection.second.x), |
|
y = Math.min(prevSelection.first.y, prevSelection.second.y), |
|
w = Math.abs(prevSelection.second.x - prevSelection.first.x), |
|
h = Math.abs(prevSelection.second.y - prevSelection.first.y); |
|
|
|
this.octx.clearRect(x + plotOffset.left - lw + 0.5, |
|
y + plotOffset.top - lw, |
|
w + 2 * lw + 0.5, |
|
h + 2 * lw + 0.5); |
|
|
|
this.selection.prevSelection = null; |
|
}, |
|
/** |
|
* Determines whether or not the selection is sane and should be drawn. |
|
* @return {Boolean} - True when sane, false otherwise. |
|
*/ |
|
selectionIsSane: function(){ |
|
var s = this.selection.selection; |
|
return Math.abs(s.second.x - s.first.x) >= 5 || |
|
Math.abs(s.second.y - s.first.y) >= 5; |
|
} |
|
|
|
}); |
|
|
|
})(); |
|
|