--- a/js/flotr2/js/plugins/selection.js +++ b/js/flotr2/js/plugins/selection.js @@ -1,1 +1,278 @@ - +/** + * 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; + } + +}); + +})(); +