3 * Copyright 2011 Paul Felix (paul.eric.felix@gmail.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
6 /*global Dygraph:false,TouchEvent:false */
9 * @fileoverview This file contains the RangeSelector plugin used to provide
10 * a timeline range selector widget for dygraphs.
13 Dygraph
.Plugins
.RangeSelector
= (function() {
15 /*jshint globalstrict: true */
16 /*global Dygraph:false */
19 var rangeSelector
= function() {
20 this.isIE_
= /MSIE/.test(navigator
.userAgent
) && !window
.opera
;
21 this.hasTouchInterface_
= typeof(TouchEvent
) != 'undefined';
22 this.isMobileDevice_
= /mobile|android/gi.test(navigator
.appVersion
);
23 this.interfaceCreated_
= false;
26 rangeSelector
.prototype.toString
= function() {
27 return "RangeSelector Plugin";
30 rangeSelector
.prototype.activate
= function(dygraph
) {
31 this.dygraph_
= dygraph
;
32 this.isUsingExcanvas_
= dygraph
.isUsingExcanvas_
;
33 if (this.getOption_('showRangeSelector')) {
34 this.createInterface_();
37 layout
: this.reserveSpace_
,
38 predraw
: this.renderStaticLayer_
,
39 didDrawChart
: this.renderInteractiveLayer_
43 rangeSelector
.prototype.destroy
= function() {
44 this.bgcanvas_
= null;
45 this.fgcanvas_
= null;
46 this.leftZoomHandle_
= null;
47 this.rightZoomHandle_
= null;
48 this.iePanOverlay_
= null;
51 //------------------------------------------------------------------
53 //------------------------------------------------------------------
55 rangeSelector
.prototype.getOption_
= function(name
) {
56 return this.dygraph_
.getOption(name
);
59 rangeSelector
.prototype.setDefaultOption_
= function(name
, value
) {
60 return this.dygraph_
.attrs_
[name
] = value
;
65 * Creates the range selector elements and adds them to the graph.
67 rangeSelector
.prototype.createInterface_
= function() {
68 this.createCanvases_();
69 if (this.isUsingExcanvas_
) {
70 this.createIEPanOverlay_();
72 this.createZoomHandles_();
73 this.initInteraction_();
75 // Range selector and animatedZooms have a bad interaction. See issue 359.
76 if (this.getOption_('animatedZooms')) {
77 Dygraph
.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.');
78 this.dygraph_
.updateOptions({animatedZooms
: false}, true);
81 this.interfaceCreated_
= true;
87 * Adds the range selector to the graph.
89 rangeSelector
.prototype.addToGraph_
= function() {
90 var graphDiv
= this.graphDiv_
= this.dygraph_
.graphDiv
;
91 graphDiv
.appendChild(this.bgcanvas_
);
92 graphDiv
.appendChild(this.fgcanvas_
);
93 graphDiv
.appendChild(this.leftZoomHandle_
);
94 graphDiv
.appendChild(this.rightZoomHandle_
);
99 * Removes the range selector from the graph.
101 rangeSelector
.prototype.removeFromGraph_
= function() {
102 var graphDiv
= this.graphDiv_
;
103 graphDiv
.removeChild(this.bgcanvas_
);
104 graphDiv
.removeChild(this.fgcanvas_
);
105 graphDiv
.removeChild(this.leftZoomHandle_
);
106 graphDiv
.removeChild(this.rightZoomHandle_
);
107 this.graphDiv_
= null;
112 * Called by Layout to allow range selector to reserve its space.
114 rangeSelector
.prototype.reserveSpace_
= function(e
) {
115 if (this.getOption_('showRangeSelector')) {
116 e
.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4);
122 * Renders the static portion of the range selector at the predraw stage.
124 rangeSelector
.prototype.renderStaticLayer_
= function() {
125 if (!this.updateVisibility_()) {
129 this.drawStaticLayer_();
134 * Renders the interactive portion of the range selector after the chart has been drawn.
136 rangeSelector
.prototype.renderInteractiveLayer_
= function() {
137 if (!this.updateVisibility_() || this.isChangingRange_
) {
140 this.placeZoomHandles_();
141 this.drawInteractiveLayer_();
146 * Check to see if the range selector is enabled/disabled and update visibility accordingly.
148 rangeSelector
.prototype.updateVisibility_
= function() {
149 var enabled
= this.getOption_('showRangeSelector');
151 if (!this.interfaceCreated_
) {
152 this.createInterface_();
153 } else if (!this.graphDiv_
|| !this.graphDiv_
.parentNode
) {
156 } else if (this.graphDiv_
) {
157 this.removeFromGraph_();
158 var dygraph
= this.dygraph_
;
159 setTimeout(function() { dygraph
.width_
= 0; dygraph
.resize(); }, 1);
166 * Resizes the range selector.
168 rangeSelector
.prototype.resize_
= function() {
169 function setElementRect(canvas
, rect
) {
170 canvas
.style
.top
= rect
.y
+ 'px';
171 canvas
.style
.left
= rect
.x
+ 'px';
172 canvas
.width
= rect
.w
;
173 canvas
.height
= rect
.h
;
174 canvas
.style
.width
= canvas
.width
+ 'px'; // for IE
175 canvas
.style
.height
= canvas
.height
+ 'px'; // for IE
178 var plotArea
= this.dygraph_
.layout_
.getPlotArea();
180 var xAxisLabelHeight
= 0;
181 if (this.dygraph_
.getOptionForAxis('drawAxis', 'x')) {
182 xAxisLabelHeight
= this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'));
186 y
: plotArea
.y
+ plotArea
.h
+ xAxisLabelHeight
+ 4,
188 h
: this.getOption_('rangeSelectorHeight')
191 setElementRect(this.bgcanvas_
, this.canvasRect_
);
192 setElementRect(this.fgcanvas_
, this.canvasRect_
);
197 * Creates the background and foreground canvases.
199 rangeSelector
.prototype.createCanvases_
= function() {
200 this.bgcanvas_
= Dygraph
.createCanvas();
201 this.bgcanvas_
.className
= 'dygraph-rangesel-bgcanvas';
202 this.bgcanvas_
.style
.position
= 'absolute';
203 this.bgcanvas_
.style
.zIndex
= 9;
204 this.bgcanvas_ctx_
= Dygraph
.getContext(this.bgcanvas_
);
206 this.fgcanvas_
= Dygraph
.createCanvas();
207 this.fgcanvas_
.className
= 'dygraph-rangesel-fgcanvas';
208 this.fgcanvas_
.style
.position
= 'absolute';
209 this.fgcanvas_
.style
.zIndex
= 9;
210 this.fgcanvas_
.style
.cursor
= 'default';
211 this.fgcanvas_ctx_
= Dygraph
.getContext(this.fgcanvas_
);
216 * Creates overlay divs for IE/Excanvas so that mouse events are handled properly.
218 rangeSelector
.prototype.createIEPanOverlay_
= function() {
219 this.iePanOverlay_
= document
.createElement("div");
220 this.iePanOverlay_
.style
.position
= 'absolute';
221 this.iePanOverlay_
.style
.backgroundColor
= 'white';
222 this.iePanOverlay_
.style
.filter
= 'alpha(opacity=0)';
223 this.iePanOverlay_
.style
.display
= 'none';
224 this.iePanOverlay_
.style
.cursor
= 'move';
225 this.fgcanvas_
.appendChild(this.iePanOverlay_
);
230 * Creates the zoom handle elements.
232 rangeSelector
.prototype.createZoomHandles_
= function() {
233 var img
= new Image();
234 img
.className
= 'dygraph-rangesel-zoomhandle';
235 img
.style
.position
= 'absolute';
236 img
.style
.zIndex
= 10;
237 img
.style
.visibility
= 'hidden'; // Initially hidden so they don't show up in the wrong place.
238 img
.style
.cursor
= 'col-resize';
240 if (/MSIE 7/.test(navigator
.userAgent
)) { // IE7 doesn't support embedded src data.
243 img
.style
.backgroundColor
= 'white';
244 img
.style
.border
= '1px solid #333333'; // Just show box in IE7.
248 img
.src
= 'data:image/png;base64,' +
249 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' +
250 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' +
251 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' +
252 '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' +
253 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=';
256 if (this.isMobileDevice_
) {
261 this.leftZoomHandle_
= img
;
262 this.rightZoomHandle_
= img
.cloneNode(false);
267 * Sets up the interaction for the range selector.
269 rangeSelector
.prototype.initInteraction_
= function() {
271 var topElem
= this.isIE_
? document
: window
;
274 var isZooming
= false;
275 var isPanning
= false;
276 var dynamic
= !this.isMobileDevice_
&& !this.isUsingExcanvas_
;
278 // We cover iframes during mouse interactions. See comments in
279 // dygraph-utils.js for more info on why this is a good idea.
280 var tarp
= new Dygraph
.IFrameTarp();
282 // functions, defined below. Defining them this way (rather than with
283 // "function foo() {...}" makes JSHint happy.
284 var toXDataWindow
, onZoomStart
, onZoom
, onZoomEnd
, doZoom
, isMouseInPanZone
,
285 onPanStart
, onPan
, onPanEnd
, doPan
, onCanvasHover
;
287 // Touch event functions
288 var onZoomHandleTouchEvent
, onCanvasTouchEvent
, addTouchEvents
;
290 toXDataWindow
= function(zoomHandleStatus
) {
291 var xDataLimits
= self
.dygraph_
.xAxisExtremes();
292 var fact
= (xDataLimits
[1] - xDataLimits
[0])/self
.canvasRect_
.w
;
293 var xDataMin
= xDataLimits
[0] + (zoomHandleStatus
.leftHandlePos
- self
.canvasRect_
.x
)*fact
;
294 var xDataMax
= xDataLimits
[0] + (zoomHandleStatus
.rightHandlePos
- self
.canvasRect_
.x
)*fact
;
295 return [xDataMin
, xDataMax
];
298 onZoomStart
= function(e
) {
299 Dygraph
.cancelEvent(e
);
301 clientXLast
= e
.clientX
;
302 handle
= e
.target
? e
.target
: e
.srcElement
;
303 if (e
.type
=== 'mousedown' || e
.type
=== 'dragstart') {
304 // These events are removed manually.
305 Dygraph
.addEvent(topElem
, 'mousemove', onZoom
);
306 Dygraph
.addEvent(topElem
, 'mouseup', onZoomEnd
);
308 self
.fgcanvas_
.style
.cursor
= 'col-resize';
313 onZoom
= function(e
) {
317 Dygraph
.cancelEvent(e
);
319 var delX
= e
.clientX
- clientXLast
;
320 if (Math
.abs(delX
) < 4) {
323 clientXLast
= e
.clientX
;
326 var zoomHandleStatus
= self
.getZoomHandleStatus_();
328 if (handle
== self
.leftZoomHandle_
) {
329 newPos
= zoomHandleStatus
.leftHandlePos
+ delX
;
330 newPos
= Math
.min(newPos
, zoomHandleStatus
.rightHandlePos
- handle
.width
- 3);
331 newPos
= Math
.max(newPos
, self
.canvasRect_
.x
);
333 newPos
= zoomHandleStatus
.rightHandlePos
+ delX
;
334 newPos
= Math
.min(newPos
, self
.canvasRect_
.x
+ self
.canvasRect_
.w
);
335 newPos
= Math
.max(newPos
, zoomHandleStatus
.leftHandlePos
+ handle
.width
+ 3);
337 var halfHandleWidth
= handle
.width
/2;
338 handle
.style
.left
= (newPos
- halfHandleWidth
) + 'px';
339 self
.drawInteractiveLayer_();
341 // Zoom on the fly (if not using excanvas).
348 onZoomEnd
= function(e
) {
354 Dygraph
.removeEvent(topElem
, 'mousemove', onZoom
);
355 Dygraph
.removeEvent(topElem
, 'mouseup', onZoomEnd
);
356 self
.fgcanvas_
.style
.cursor
= 'default';
358 // If using excanvas, Zoom now.
365 doZoom
= function() {
367 var zoomHandleStatus
= self
.getZoomHandleStatus_();
368 self
.isChangingRange_
= true;
369 if (!zoomHandleStatus
.isZoomed
) {
370 self
.dygraph_
.resetZoom();
372 var xDataWindow
= toXDataWindow(zoomHandleStatus
);
373 self
.dygraph_
.doZoomXDates_(xDataWindow
[0], xDataWindow
[1]);
376 self
.isChangingRange_
= false;
380 isMouseInPanZone
= function(e
) {
381 if (self
.isUsingExcanvas_
) {
382 return e
.srcElement
== self
.iePanOverlay_
;
384 var rect
= self
.leftZoomHandle_
.getBoundingClientRect();
385 var leftHandleClientX
= rect
.left
+ rect
.width
/2;
386 rect
= self
.rightZoomHandle_
.getBoundingClientRect();
387 var rightHandleClientX
= rect
.left
+ rect
.width
/2;
388 return (e
.clientX
> leftHandleClientX
&& e
.clientX
< rightHandleClientX
);
392 onPanStart
= function(e
) {
393 if (!isPanning
&& isMouseInPanZone(e
) && self
.getZoomHandleStatus_().isZoomed
) {
394 Dygraph
.cancelEvent(e
);
396 clientXLast
= e
.clientX
;
397 if (e
.type
=== 'mousedown') {
398 // These events are removed manually.
399 Dygraph
.addEvent(topElem
, 'mousemove', onPan
);
400 Dygraph
.addEvent(topElem
, 'mouseup', onPanEnd
);
407 onPan
= function(e
) {
411 Dygraph
.cancelEvent(e
);
413 var delX
= e
.clientX
- clientXLast
;
414 if (Math
.abs(delX
) < 4) {
417 clientXLast
= e
.clientX
;
420 var zoomHandleStatus
= self
.getZoomHandleStatus_();
421 var leftHandlePos
= zoomHandleStatus
.leftHandlePos
;
422 var rightHandlePos
= zoomHandleStatus
.rightHandlePos
;
423 var rangeSize
= rightHandlePos
- leftHandlePos
;
424 if (leftHandlePos
+ delX
<= self
.canvasRect_
.x
) {
425 leftHandlePos
= self
.canvasRect_
.x
;
426 rightHandlePos
= leftHandlePos
+ rangeSize
;
427 } else if (rightHandlePos
+ delX
>= self
.canvasRect_
.x
+ self
.canvasRect_
.w
) {
428 rightHandlePos
= self
.canvasRect_
.x
+ self
.canvasRect_
.w
;
429 leftHandlePos
= rightHandlePos
- rangeSize
;
431 leftHandlePos
+= delX
;
432 rightHandlePos
+= delX
;
434 var halfHandleWidth
= self
.leftZoomHandle_
.width
/2;
435 self
.leftZoomHandle_
.style
.left
= (leftHandlePos
- halfHandleWidth
) + 'px';
436 self
.rightZoomHandle_
.style
.left
= (rightHandlePos
- halfHandleWidth
) + 'px';
437 self
.drawInteractiveLayer_();
439 // Do pan on the fly (if not using excanvas).
446 onPanEnd
= function(e
) {
451 Dygraph
.removeEvent(topElem
, 'mousemove', onPan
);
452 Dygraph
.removeEvent(topElem
, 'mouseup', onPanEnd
);
453 // If using excanvas, do pan now.
462 self
.isChangingRange_
= true;
463 self
.dygraph_
.dateWindow_
= toXDataWindow(self
.getZoomHandleStatus_());
464 self
.dygraph_
.drawGraph_(false);
466 self
.isChangingRange_
= false;
470 onCanvasHover
= function(e
) {
471 if (isZooming
|| isPanning
) {
474 var cursor
= isMouseInPanZone(e
) ? 'move' : 'default';
475 if (cursor
!= self
.fgcanvas_
.style
.cursor
) {
476 self
.fgcanvas_
.style
.cursor
= cursor
;
480 onZoomHandleTouchEvent
= function(e
) {
481 if (e
.type
== 'touchstart' && e
.targetTouches
.length
== 1) {
482 if (onZoomStart(e
.targetTouches
[0])) {
483 Dygraph
.cancelEvent(e
);
485 } else if (e
.type
== 'touchmove' && e
.targetTouches
.length
== 1) {
486 if (onZoom(e
.targetTouches
[0])) {
487 Dygraph
.cancelEvent(e
);
494 onCanvasTouchEvent
= function(e
) {
495 if (e
.type
== 'touchstart' && e
.targetTouches
.length
== 1) {
496 if (onPanStart(e
.targetTouches
[0])) {
497 Dygraph
.cancelEvent(e
);
499 } else if (e
.type
== 'touchmove' && e
.targetTouches
.length
== 1) {
500 if (onPan(e
.targetTouches
[0])) {
501 Dygraph
.cancelEvent(e
);
508 addTouchEvents
= function(elem
, fn
) {
509 var types
= ['touchstart', 'touchend', 'touchmove', 'touchcancel'];
510 for (var i
= 0; i
< types
.length
; i
++) {
511 self
.dygraph_
.addAndTrackEvent(elem
, types
[i
], fn
);
515 this.setDefaultOption_('interactionModel', Dygraph
.Interaction
.dragIsPanInteractionModel
);
516 this.setDefaultOption_('panEdgeFraction', 0.0001);
518 var dragStartEvent
= window
.opera
? 'mousedown' : 'dragstart';
519 this.dygraph_
.addAndTrackEvent(this.leftZoomHandle_
, dragStartEvent
, onZoomStart
);
520 this.dygraph_
.addAndTrackEvent(this.rightZoomHandle_
, dragStartEvent
, onZoomStart
);
522 if (this.isUsingExcanvas_
) {
523 this.dygraph_
.addAndTrackEvent(this.iePanOverlay_
, 'mousedown', onPanStart
);
525 this.dygraph_
.addAndTrackEvent(this.fgcanvas_
, 'mousedown', onPanStart
);
526 this.dygraph_
.addAndTrackEvent(this.fgcanvas_
, 'mousemove', onCanvasHover
);
530 if (this.hasTouchInterface_
) {
531 addTouchEvents(this.leftZoomHandle_
, onZoomHandleTouchEvent
);
532 addTouchEvents(this.rightZoomHandle_
, onZoomHandleTouchEvent
);
533 addTouchEvents(this.fgcanvas_
, onCanvasTouchEvent
);
539 * Draws the static layer in the background canvas.
541 rangeSelector
.prototype.drawStaticLayer_
= function() {
542 var ctx
= this.bgcanvas_ctx_
;
543 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
545 this.drawMiniPlot_();
551 this.bgcanvas_ctx_
.lineWidth
= 1;
552 ctx
.strokeStyle
= 'gray';
554 ctx
.moveTo(margin
, margin
);
555 ctx
.lineTo(margin
, this.canvasRect_
.h
-margin
);
556 ctx
.lineTo(this.canvasRect_
.w
-margin
, this.canvasRect_
.h
-margin
);
557 ctx
.lineTo(this.canvasRect_
.w
-margin
, margin
);
564 * Draws the mini plot in the background canvas.
566 rangeSelector
.prototype.drawMiniPlot_
= function() {
567 var fillStyle
= this.getOption_('rangeSelectorPlotFillColor');
568 var strokeStyle
= this.getOption_('rangeSelectorPlotStrokeColor');
569 if (!fillStyle
&& !strokeStyle
) {
573 var stepPlot
= this.getOption_('stepPlot');
575 var combinedSeriesData
= this.computeCombinedSeriesAndLimits_();
576 var yRange
= combinedSeriesData
.yMax
- combinedSeriesData
.yMin
;
578 // Draw the mini plot.
579 var ctx
= this.bgcanvas_ctx_
;
582 var xExtremes
= this.dygraph_
.xAxisExtremes();
583 var xRange
= Math
.max(xExtremes
[1] - xExtremes
[0], 1.e
-30);
584 var xFact
= (this.canvasRect_
.w
- margin
)/xRange
;
585 var yFact
= (this.canvasRect_
.h
- margin
)/yRange
;
586 var canvasWidth
= this.canvasRect_
.w
- margin
;
587 var canvasHeight
= this.canvasRect_
.h
- margin
;
589 var prevX
= null, prevY
= null;
592 ctx
.moveTo(margin
, canvasHeight
);
593 for (var i
= 0; i
< combinedSeriesData
.data
.length
; i
++) {
594 var dataPoint
= combinedSeriesData
.data
[i
];
595 var x
= ((dataPoint
[0] !== null) ? ((dataPoint
[0] - xExtremes
[0])*xFact
) : NaN
);
596 var y
= ((dataPoint
[1] !== null) ? (canvasHeight
- (dataPoint
[1] - combinedSeriesData
.yMin
)*yFact
) : NaN
);
597 if (isFinite(x
) && isFinite(y
)) {
599 ctx
.lineTo(x
, canvasHeight
);
602 ctx
.lineTo(x
, prevY
);
611 ctx
.lineTo(x
, prevY
);
612 ctx
.lineTo(x
, canvasHeight
);
615 ctx
.lineTo(prevX
, canvasHeight
);
618 prevX
= prevY
= null;
621 ctx
.lineTo(canvasWidth
, canvasHeight
);
625 var lingrad
= this.bgcanvas_ctx_
.createLinearGradient(0, 0, 0, canvasHeight
);
626 lingrad
.addColorStop(0, 'white');
627 lingrad
.addColorStop(1, fillStyle
);
628 this.bgcanvas_ctx_
.fillStyle
= lingrad
;
633 this.bgcanvas_ctx_
.strokeStyle
= strokeStyle
;
634 this.bgcanvas_ctx_
.lineWidth
= 1.5;
641 * Computes and returns the combined series data along with min/max for the mini plot.
642 * The combined series consists of averaged values for all series.
643 * When series have error bars, the error bars are ignored.
644 * @return {Object} An object containing combined series array, ymin, ymax.
646 rangeSelector
.prototype.computeCombinedSeriesAndLimits_
= function() {
647 var g
= this.dygraph_
;
648 var data
= g
.rawData_
;
649 var logscale
= this.getOption_('logscale');
651 // Create a combined series (average of all series values).
658 // TODO(danvk): short-circuit if there's only one series.
659 var rolledSeries
= [];
660 var dataHandler
= g
.dataHandler_
;
661 var options
= g
.attributes_
;
662 for (var i
= 1; i
< g
.numColumns(); i
++) {
663 var series
= dataHandler
.extractSeries(g
.rawData_
, i
, options
);
664 if (g
.rollPeriod() > 1) {
665 series
= dataHandler
.rollingAverage(series
, g
.rollPeriod(), options
);
668 rolledSeries
.push(series
);
671 var combinedSeries
= [];
672 for (i
= 0; i
< rolledSeries
[0].length
; i
++) {
675 for (j
= 0; j
< rolledSeries
.length
; j
++) {
676 var y
= rolledSeries
[j
][i
][1];
677 if (y
=== null || isNaN(y
)) continue;
681 combinedSeries
.push([rolledSeries
[0][i
][0], sum
/ count
]);
684 // Compute the y range.
685 var yMin
= Number
.MAX_VALUE
;
686 var yMax
= -Number
.MAX_VALUE
;
687 for (i
= 0; i
< combinedSeries
.length
; i
++) {
688 yVal
= combinedSeries
[i
][1];
689 if (yVal
!== null && isFinite(yVal
) && (!logscale
|| yVal
> 0)) {
690 yMin
= Math
.min(yMin
, yVal
);
691 yMax
= Math
.max(yMax
, yVal
);
695 // Convert Y data to log scale if needed.
696 // Also, expand the Y range to compress the mini plot a little.
697 var extraPercent
= 0.25;
699 yMax
= Dygraph
.log10(yMax
);
700 yMax
+= yMax
*extraPercent
;
701 yMin
= Dygraph
.log10(yMin
);
702 for (i
= 0; i
< combinedSeries
.length
; i
++) {
703 combinedSeries
[i
][1] = Dygraph
.log10(combinedSeries
[i
][1]);
707 var yRange
= yMax
- yMin
;
708 if (yRange
<= Number
.MIN_VALUE
) {
709 yExtra
= yMax
*extraPercent
;
711 yExtra
= yRange
*extraPercent
;
717 return {data
: combinedSeries
, yMin
: yMin
, yMax
: yMax
};
722 * Places the zoom handles in the proper position based on the current X data window.
724 rangeSelector
.prototype.placeZoomHandles_
= function() {
725 var xExtremes
= this.dygraph_
.xAxisExtremes();
726 var xWindowLimits
= this.dygraph_
.xAxisRange();
727 var xRange
= xExtremes
[1] - xExtremes
[0];
728 var leftPercent
= Math
.max(0, (xWindowLimits
[0] - xExtremes
[0])/xRange
);
729 var rightPercent
= Math
.max(0, (xExtremes
[1] - xWindowLimits
[1])/xRange
);
730 var leftCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*leftPercent
;
731 var rightCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*(1 - rightPercent
);
732 var handleTop
= Math
.max(this.canvasRect_
.y
, this.canvasRect_
.y
+ (this.canvasRect_
.h
- this.leftZoomHandle_
.height
)/2);
733 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
734 this.leftZoomHandle_
.style
.left
= (leftCoord
- halfHandleWidth
) + 'px';
735 this.leftZoomHandle_
.style
.top
= handleTop
+ 'px';
736 this.rightZoomHandle_
.style
.left
= (rightCoord
- halfHandleWidth
) + 'px';
737 this.rightZoomHandle_
.style
.top
= this.leftZoomHandle_
.style
.top
;
739 this.leftZoomHandle_
.style
.visibility
= 'visible';
740 this.rightZoomHandle_
.style
.visibility
= 'visible';
745 * Draws the interactive layer in the foreground canvas.
747 rangeSelector
.prototype.drawInteractiveLayer_
= function() {
748 var ctx
= this.fgcanvas_ctx_
;
749 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
751 var width
= this.canvasRect_
.w
- margin
;
752 var height
= this.canvasRect_
.h
- margin
;
753 var zoomHandleStatus
= this.getZoomHandleStatus_();
755 ctx
.strokeStyle
= 'black';
756 if (!zoomHandleStatus
.isZoomed
) {
758 ctx
.moveTo(margin
, margin
);
759 ctx
.lineTo(margin
, height
);
760 ctx
.lineTo(width
, height
);
761 ctx
.lineTo(width
, margin
);
763 if (this.iePanOverlay_
) {
764 this.iePanOverlay_
.style
.display
= 'none';
767 var leftHandleCanvasPos
= Math
.max(margin
, zoomHandleStatus
.leftHandlePos
- this.canvasRect_
.x
);
768 var rightHandleCanvasPos
= Math
.min(width
, zoomHandleStatus
.rightHandlePos
- this.canvasRect_
.x
);
770 ctx
.fillStyle
= 'rgba(240, 240, 240, 0.6)';
771 ctx
.fillRect(0, 0, leftHandleCanvasPos
, this.canvasRect_
.h
);
772 ctx
.fillRect(rightHandleCanvasPos
, 0, this.canvasRect_
.w
- rightHandleCanvasPos
, this.canvasRect_
.h
);
775 ctx
.moveTo(margin
, margin
);
776 ctx
.lineTo(leftHandleCanvasPos
, margin
);
777 ctx
.lineTo(leftHandleCanvasPos
, height
);
778 ctx
.lineTo(rightHandleCanvasPos
, height
);
779 ctx
.lineTo(rightHandleCanvasPos
, margin
);
780 ctx
.lineTo(width
, margin
);
783 if (this.isUsingExcanvas_
) {
784 this.iePanOverlay_
.style
.width
= (rightHandleCanvasPos
- leftHandleCanvasPos
) + 'px';
785 this.iePanOverlay_
.style
.left
= leftHandleCanvasPos
+ 'px';
786 this.iePanOverlay_
.style
.height
= height
+ 'px';
787 this.iePanOverlay_
.style
.display
= 'inline';
794 * Returns the current zoom handle position information.
795 * @return {Object} The zoom handle status.
797 rangeSelector
.prototype.getZoomHandleStatus_
= function() {
798 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
799 var leftHandlePos
= parseFloat(this.leftZoomHandle_
.style
.left
) + halfHandleWidth
;
800 var rightHandlePos
= parseFloat(this.rightZoomHandle_
.style
.left
) + halfHandleWidth
;
802 leftHandlePos
: leftHandlePos
,
803 rightHandlePos
: rightHandlePos
,
804 isZoomed
: (leftHandlePos
- 1 > this.canvasRect_
.x
|| rightHandlePos
+ 1 < this.canvasRect_
.x
+this.canvasRect_
.w
)
808 return rangeSelector
;