3 * Copyright 2011 Paul Felix (paul.eric.felix@gmail.com)
4 * MIT-licensed (http://opensource.org/licenses/MIT)
8 * @fileoverview This file contains the DygraphRangeSelector class used to provide
9 * a timeline range selector widget for dygraphs.
12 /*jshint globalstrict: true */
13 /*global Dygraph:false */
17 * The DygraphRangeSelector class provides a timeline range selector widget.
18 * @param {Dygraph} dygraph The dygraph object
21 var DygraphRangeSelector
= function(dygraph
) {
22 this.isIE_
= /MSIE/.test(navigator
.userAgent
) && !window
.opera
;
23 this.isUsingExcanvas_
= dygraph
.isUsingExcanvas_
;
24 this.dygraph_
= dygraph
;
25 this.hasTouchInterface_
= typeof(TouchEvent
) != 'undefined';
26 this.isMobileDevice_
= /mobile|android/gi.test(navigator
.appVersion
);
27 this.createCanvases_();
28 if (this.isUsingExcanvas_
) {
29 this.createIEPanOverlay_();
31 this.createZoomHandles_();
32 this.initInteraction_();
36 * Adds the range selector to the dygraph.
37 * @param {Object} graphDiv The container div for the range selector.
38 * @param {DygraphLayout} layout The DygraphLayout object for this graph.
40 DygraphRangeSelector
.prototype.addToGraph
= function(graphDiv
, layout
) {
41 this.layout_
= layout
;
43 graphDiv
.appendChild(this.bgcanvas_
);
44 graphDiv
.appendChild(this.fgcanvas_
);
45 graphDiv
.appendChild(this.leftZoomHandle_
);
46 graphDiv
.appendChild(this.rightZoomHandle_
);
50 * Renders the static background portion of the range selector.
52 DygraphRangeSelector
.prototype.renderStaticLayer
= function() {
54 this.drawStaticLayer_();
58 * Renders the interactive foreground portion of the range selector.
60 DygraphRangeSelector
.prototype.renderInteractiveLayer
= function() {
61 if (this.isChangingRange_
) {
64 this.placeZoomHandles_();
65 this.drawInteractiveLayer_();
70 * Resizes the range selector.
72 DygraphRangeSelector
.prototype.resize_
= function() {
73 function setElementRect(canvas
, rect
) {
74 canvas
.style
.top
= rect
.y
+ 'px';
75 canvas
.style
.left
= rect
.x
+ 'px';
76 canvas
.width
= rect
.w
;
77 canvas
.height
= rect
.h
;
78 canvas
.style
.width
= canvas
.width
+ 'px'; // for IE
79 canvas
.style
.height
= canvas
.height
+ 'px'; // for IE
82 var plotArea
= this.layout_
.getPlotArea();
83 var xAxisLabelHeight
= this.attr_('xAxisHeight') || (this.attr_('axisLabelFontSize') + 2 * this.attr_('axisTickSize'));
86 y
: plotArea
.y
+ plotArea
.h
+ xAxisLabelHeight
+ 4,
88 h
: this.attr_('rangeSelectorHeight')
91 setElementRect(this.bgcanvas_
, this.canvasRect_
);
92 setElementRect(this.fgcanvas_
, this.canvasRect_
);
95 DygraphRangeSelector
.prototype.attr_
= function(name
) {
96 return this.dygraph_
.attr_(name
);
101 * Creates the background and foreground canvases.
103 DygraphRangeSelector
.prototype.createCanvases_
= function() {
104 this.bgcanvas_
= Dygraph
.createCanvas();
105 this.bgcanvas_
.className
= 'dygraph-rangesel-bgcanvas';
106 this.bgcanvas_
.style
.position
= 'absolute';
107 this.bgcanvas_
.style
.zIndex
= 9;
108 this.bgcanvas_ctx_
= Dygraph
.getContext(this.bgcanvas_
);
110 this.fgcanvas_
= Dygraph
.createCanvas();
111 this.fgcanvas_
.className
= 'dygraph-rangesel-fgcanvas';
112 this.fgcanvas_
.style
.position
= 'absolute';
113 this.fgcanvas_
.style
.zIndex
= 9;
114 this.fgcanvas_
.style
.cursor
= 'default';
115 this.fgcanvas_ctx_
= Dygraph
.getContext(this.fgcanvas_
);
120 * Creates overlay divs for IE/Excanvas so that mouse events are handled properly.
122 DygraphRangeSelector
.prototype.createIEPanOverlay_
= function() {
123 this.iePanOverlay_
= document
.createElement("div");
124 this.iePanOverlay_
.style
.position
= 'absolute';
125 this.iePanOverlay_
.style
.backgroundColor
= 'white';
126 this.iePanOverlay_
.style
.filter
= 'alpha(opacity=0)';
127 this.iePanOverlay_
.style
.display
= 'none';
128 this.iePanOverlay_
.style
.cursor
= 'move';
129 this.fgcanvas_
.appendChild(this.iePanOverlay_
);
134 * Creates the zoom handle elements.
136 DygraphRangeSelector
.prototype.createZoomHandles_
= function() {
137 var img
= new Image();
138 img
.className
= 'dygraph-rangesel-zoomhandle';
139 img
.style
.position
= 'absolute';
140 img
.style
.zIndex
= 10;
141 img
.style
.visibility
= 'hidden'; // Initially hidden so they don't show up in the wrong place.
142 img
.style
.cursor
= 'col-resize';
144 if (/MSIE 7/.test(navigator
.userAgent
)) { // IE7 doesn't support embedded src data.
147 img
.style
.backgroundColor
= 'white';
148 img
.style
.border
= '1px solid #333333'; // Just show box in IE7.
152 img
.src
= 'data:image/png;base64,' +
153 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' +
154 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' +
155 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' +
156 '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' +
157 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=';
160 if (this.isMobileDevice_
) {
165 this.leftZoomHandle_
= img
;
166 this.rightZoomHandle_
= img
.cloneNode(false);
171 * Sets up the interaction for the range selector.
173 DygraphRangeSelector
.prototype.initInteraction_
= function() {
175 var topElem
= this.isIE_
? document
: window
;
178 var isZooming
= false;
179 var isPanning
= false;
180 var dynamic
= !this.isMobileDevice_
&& !this.isUsingExcanvas_
;
182 // We cover iframes during mouse interactions. See comments in
183 // dygraph-utils.js for more info on why this is a good idea.
184 var tarp
= new Dygraph
.IFrameTarp();
186 // functions, defined below. Defining them this way (rather than with
187 // "function foo() {...}" makes JSHint happy.
188 var toXDataWindow
, onZoomStart
, onZoom
, onZoomEnd
, doZoom
, isMouseInPanZone
,
189 onPanStart
, onPan
, onPanEnd
, doPan
, onCanvasMouseMove
, applyBrowserZoomLevel
;
191 // Touch event functions
192 var onZoomHandleTouchEvent
, onCanvasTouchEvent
, addTouchEvents
;
194 toXDataWindow
= function(zoomHandleStatus
) {
195 var xDataLimits
= self
.dygraph_
.xAxisExtremes();
196 var fact
= (xDataLimits
[1] - xDataLimits
[0])/self
.canvasRect_
.w
;
197 var xDataMin
= xDataLimits
[0] + (zoomHandleStatus
.leftHandlePos
- self
.canvasRect_
.x
)*fact
;
198 var xDataMax
= xDataLimits
[0] + (zoomHandleStatus
.rightHandlePos
- self
.canvasRect_
.x
)*fact
;
199 return [xDataMin
, xDataMax
];
202 applyBrowserZoomLevel
= function(delX
) {
203 var zoom
= window
.outerWidth
/document
.documentElement
.clientWidth
;
211 onZoomStart
= function(e
) {
212 Dygraph
.cancelEvent(e
);
215 handle
= e
.target
? e
.target
: e
.srcElement
;
216 self
.dygraph_
.addEvent(topElem
, 'mousemove', onZoom
);
217 self
.dygraph_
.addEvent(topElem
, 'mouseup', onZoomEnd
);
218 self
.fgcanvas_
.style
.cursor
= 'col-resize';
223 onZoom
= function(e
) {
227 Dygraph
.cancelEvent(e
);
228 var delX
= e
.screenX
- xLast
;
229 if (Math
.abs(delX
) < 4 || e
.screenX
=== 0) {
230 // First iPad move event seems to have screenX = 0
234 delX
= applyBrowserZoomLevel(delX
);
237 var zoomHandleStatus
= self
.getZoomHandleStatus_();
239 if (handle
== self
.leftZoomHandle_
) {
240 newPos
= zoomHandleStatus
.leftHandlePos
+ delX
;
241 newPos
= Math
.min(newPos
, zoomHandleStatus
.rightHandlePos
- handle
.width
- 3);
242 newPos
= Math
.max(newPos
, self
.canvasRect_
.x
);
244 newPos
= zoomHandleStatus
.rightHandlePos
+ delX
;
245 newPos
= Math
.min(newPos
, self
.canvasRect_
.x
+ self
.canvasRect_
.w
);
246 newPos
= Math
.max(newPos
, zoomHandleStatus
.leftHandlePos
+ handle
.width
+ 3);
248 var halfHandleWidth
= handle
.width
/2;
249 handle
.style
.left
= (newPos
- halfHandleWidth
) + 'px';
250 self
.drawInteractiveLayer_();
252 // Zoom on the fly (if not using excanvas).
259 onZoomEnd
= function(e
) {
265 Dygraph
.removeEvent(topElem
, 'mousemove', onZoom
);
266 Dygraph
.removeEvent(topElem
, 'mouseup', onZoomEnd
);
267 self
.fgcanvas_
.style
.cursor
= 'default';
269 // If using excanvas, Zoom now.
276 doZoom
= function() {
278 var zoomHandleStatus
= self
.getZoomHandleStatus_();
279 self
.isChangingRange_
= true;
280 if (!zoomHandleStatus
.isZoomed
) {
281 self
.dygraph_
.doUnzoom_();
283 var xDataWindow
= toXDataWindow(zoomHandleStatus
);
284 self
.dygraph_
.doZoomXDates_(xDataWindow
[0], xDataWindow
[1]);
287 self
.isChangingRange_
= false;
291 isMouseInPanZone
= function(e
) {
292 if (self
.isUsingExcanvas_
) {
293 return e
.srcElement
== self
.iePanOverlay_
;
295 var rect
= self
.leftZoomHandle_
.getBoundingClientRect();
296 var leftHandleClientX
= rect
.left
+ rect
.width
/2;
297 rect
= self
.rightZoomHandle_
.getBoundingClientRect();
298 var rightHandleClientX
= rect
.left
+ rect
.width
/2;
299 return (e
.clientX
> leftHandleClientX
&& e
.clientX
< rightHandleClientX
);
303 onPanStart
= function(e
) {
304 if (!isPanning
&& isMouseInPanZone(e
) && self
.getZoomHandleStatus_().isZoomed
) {
305 Dygraph
.cancelEvent(e
);
308 self
.dygraph_
.addEvent(topElem
, 'mousemove', onPan
);
309 self
.dygraph_
.addEvent(topElem
, 'mouseup', onPanEnd
);
315 onPan
= function(e
) {
319 Dygraph
.cancelEvent(e
);
321 var delX
= e
.screenX
- xLast
;
322 if (Math
.abs(delX
) < 4) {
326 delX
= applyBrowserZoomLevel(delX
);
329 var zoomHandleStatus
= self
.getZoomHandleStatus_();
330 var leftHandlePos
= zoomHandleStatus
.leftHandlePos
;
331 var rightHandlePos
= zoomHandleStatus
.rightHandlePos
;
332 var rangeSize
= rightHandlePos
- leftHandlePos
;
333 if (leftHandlePos
+ delX
<= self
.canvasRect_
.x
) {
334 leftHandlePos
= self
.canvasRect_
.x
;
335 rightHandlePos
= leftHandlePos
+ rangeSize
;
336 } else if (rightHandlePos
+ delX
>= self
.canvasRect_
.x
+ self
.canvasRect_
.w
) {
337 rightHandlePos
= self
.canvasRect_
.x
+ self
.canvasRect_
.w
;
338 leftHandlePos
= rightHandlePos
- rangeSize
;
340 leftHandlePos
+= delX
;
341 rightHandlePos
+= delX
;
343 var halfHandleWidth
= self
.leftZoomHandle_
.width
/2;
344 self
.leftZoomHandle_
.style
.left
= (leftHandlePos
- halfHandleWidth
) + 'px';
345 self
.rightZoomHandle_
.style
.left
= (rightHandlePos
- halfHandleWidth
) + 'px';
346 self
.drawInteractiveLayer_();
348 // Do pan on the fly (if not using excanvas).
355 onPanEnd
= function(e
) {
360 Dygraph
.removeEvent(topElem
, 'mousemove', onPan
);
361 Dygraph
.removeEvent(topElem
, 'mouseup', onPanEnd
);
362 // If using excanvas, do pan now.
371 self
.isChangingRange_
= true;
372 self
.dygraph_
.dateWindow_
= toXDataWindow(self
.getZoomHandleStatus_());
373 self
.dygraph_
.drawGraph_(false);
375 self
.isChangingRange_
= false;
379 onCanvasMouseMove
= function(e
) {
380 if (isZooming
|| isPanning
) {
383 var cursor
= isMouseInPanZone(e
) ? 'move' : 'default';
384 if (cursor
!= self
.fgcanvas_
.style
.cursor
) {
385 self
.fgcanvas_
.style
.cursor
= cursor
;
389 onZoomHandleTouchEvent
= function(e
) {
390 if (e
.type
== 'touchstart' && e
.targetTouches
.length
== 1) {
391 if (onZoomStart(e
.targetTouches
[0])) {
392 Dygraph
.cancelEvent(e
);
394 } else if (e
.type
== 'touchmove' && e
.targetTouches
.length
== 1) {
395 if (onZoom(e
.targetTouches
[0])) {
396 Dygraph
.cancelEvent(e
);
403 onCanvasTouchEvent
= function(e
) {
404 if (e
.type
== 'touchstart' && e
.targetTouches
.length
== 1) {
405 if (onPanStart(e
.targetTouches
[0])) {
406 Dygraph
.cancelEvent(e
);
408 } else if (e
.type
== 'touchmove' && e
.targetTouches
.length
== 1) {
409 if (onPan(e
.targetTouches
[0])) {
410 Dygraph
.cancelEvent(e
);
417 addTouchEvents
= function(elem
, fn
) {
418 var types
= ['touchstart', 'touchend', 'touchmove', 'touchcancel'];
419 for (var i
= 0; i
< types
.length
; i
++) {
420 self
.dygraph_
.addEvent(elem
, types
[i
], fn
);
424 this.dygraph_
.attrs_
.interactionModel
=
425 Dygraph
.Interaction
.dragIsPanInteractionModel
;
426 this.dygraph_
.attrs_
.panEdgeFraction
= 0.0001;
428 var dragStartEvent
= window
.opera
? 'mousedown' : 'dragstart';
429 this.dygraph_
.addEvent(this.leftZoomHandle_
, dragStartEvent
, onZoomStart
);
430 this.dygraph_
.addEvent(this.rightZoomHandle_
, dragStartEvent
, onZoomStart
);
432 if (this.isUsingExcanvas_
) {
433 this.dygraph_
.addEvent(this.iePanOverlay_
, 'mousedown', onPanStart
);
435 this.dygraph_
.addEvent(this.fgcanvas_
, 'mousedown', onPanStart
);
436 this.dygraph_
.addEvent(this.fgcanvas_
, 'mousemove', onCanvasMouseMove
);
440 if (this.hasTouchInterface_
) {
441 addTouchEvents(this.leftZoomHandle_
, onZoomHandleTouchEvent
);
442 addTouchEvents(this.rightZoomHandle_
, onZoomHandleTouchEvent
);
443 addTouchEvents(this.fgcanvas_
, onCanvasTouchEvent
);
449 * Draws the static layer in the background canvas.
451 DygraphRangeSelector
.prototype.drawStaticLayer_
= function() {
452 var ctx
= this.bgcanvas_ctx_
;
453 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
455 this.drawMiniPlot_();
461 this.bgcanvas_ctx_
.lineWidth
= 1;
462 ctx
.strokeStyle
= 'gray';
464 ctx
.moveTo(margin
, margin
);
465 ctx
.lineTo(margin
, this.canvasRect_
.h
-margin
);
466 ctx
.lineTo(this.canvasRect_
.w
-margin
, this.canvasRect_
.h
-margin
);
467 ctx
.lineTo(this.canvasRect_
.w
-margin
, margin
);
474 * Draws the mini plot in the background canvas.
476 DygraphRangeSelector
.prototype.drawMiniPlot_
= function() {
477 var fillStyle
= this.attr_('rangeSelectorPlotFillColor');
478 var strokeStyle
= this.attr_('rangeSelectorPlotStrokeColor');
479 if (!fillStyle
&& !strokeStyle
) {
483 var stepPlot
= this.attr_('stepPlot');
485 var combinedSeriesData
= this.computeCombinedSeriesAndLimits_();
486 var yRange
= combinedSeriesData
.yMax
- combinedSeriesData
.yMin
;
488 // Draw the mini plot.
489 var ctx
= this.bgcanvas_ctx_
;
492 var xExtremes
= this.dygraph_
.xAxisExtremes();
493 var xRange
= Math
.max(xExtremes
[1] - xExtremes
[0], 1.e
-30);
494 var xFact
= (this.canvasRect_
.w
- margin
)/xRange
;
495 var yFact
= (this.canvasRect_
.h
- margin
)/yRange
;
496 var canvasWidth
= this.canvasRect_
.w
- margin
;
497 var canvasHeight
= this.canvasRect_
.h
- margin
;
499 var prevX
= null, prevY
= null;
502 ctx
.moveTo(margin
, canvasHeight
);
503 for (var i
= 0; i
< combinedSeriesData
.data
.length
; i
++) {
504 var dataPoint
= combinedSeriesData
.data
[i
];
505 var x
= ((dataPoint
[0] !== null) ? ((dataPoint
[0] - xExtremes
[0])*xFact
) : NaN
);
506 var y
= ((dataPoint
[1] !== null) ? (canvasHeight
- (dataPoint
[1] - combinedSeriesData
.yMin
)*yFact
) : NaN
);
507 if (isFinite(x
) && isFinite(y
)) {
509 ctx
.lineTo(x
, canvasHeight
);
512 ctx
.lineTo(x
, prevY
);
521 ctx
.lineTo(x
, prevY
);
522 ctx
.lineTo(x
, canvasHeight
);
525 ctx
.lineTo(prevX
, canvasHeight
);
528 prevX
= prevY
= null;
531 ctx
.lineTo(canvasWidth
, canvasHeight
);
535 var lingrad
= this.bgcanvas_ctx_
.createLinearGradient(0, 0, 0, canvasHeight
);
536 lingrad
.addColorStop(0, 'white');
537 lingrad
.addColorStop(1, fillStyle
);
538 this.bgcanvas_ctx_
.fillStyle
= lingrad
;
543 this.bgcanvas_ctx_
.strokeStyle
= strokeStyle
;
544 this.bgcanvas_ctx_
.lineWidth
= 1.5;
551 * Computes and returns the combinded series data along with min/max for the mini plot.
552 * @return {Object} An object containing combinded series array, ymin, ymax.
554 DygraphRangeSelector
.prototype.computeCombinedSeriesAndLimits_
= function() {
555 var data
= this.dygraph_
.rawData_
;
556 var logscale
= this.attr_('logscale');
558 // Create a combined series (average of all series values).
559 var combinedSeries
= [];
566 // Find out if data has multiple values per datapoint.
567 // Go to first data point that actually has values (see http://code.google.com/p/dygraphs/issues/detail
?id
=246)
568 for (i
= 0; i
< data
.length
; i
++) {
569 if (data
[i
].length
> 1 && data
[i
][1] !== null) {
570 mutipleValues
= typeof data
[i
][1] != 'number';
574 for (k
= 0; k
< data
[i
][1].length
; k
++) {
583 for (i
= 0; i
< data
.length
; i
++) {
584 var dataPoint
= data
[i
];
588 for (k
= 0; k
< sum
.length
; k
++) {
589 sum
[k
] = count
[k
] = 0;
595 for (j
= 1; j
< dataPoint
.length
; j
++) {
596 if (this.dygraph_
.visibility()[j
-1]) {
599 for (k
= 0; k
< sum
.length
; k
++) {
601 if (y
=== null || isNaN(y
)) continue;
607 if (y
=== null || isNaN(y
)) continue;
615 for (k
= 0; k
< sum
.length
; k
++) {
623 combinedSeries
.push([xVal
, yVal
]);
626 // Account for roll period, fractions.
627 combinedSeries
= this.dygraph_
.rollingAverage(combinedSeries
, this.dygraph_
.rollPeriod_
);
629 if (typeof combinedSeries
[0][1] != 'number') {
630 for (i
= 0; i
< combinedSeries
.length
; i
++) {
631 yVal
= combinedSeries
[i
][1];
632 combinedSeries
[i
][1] = yVal
[0];
636 // Compute the y range.
637 var yMin
= Number
.MAX_VALUE
;
638 var yMax
= -Number
.MAX_VALUE
;
639 for (i
= 0; i
< combinedSeries
.length
; i
++) {
640 yVal
= combinedSeries
[i
][1];
641 if (yVal
!== null && isFinite(yVal
) && (!logscale
|| yVal
> 0)) {
642 yMin
= Math
.min(yMin
, yVal
);
643 yMax
= Math
.max(yMax
, yVal
);
647 // Convert Y data to log scale if needed.
648 // Also, expand the Y range to compress the mini plot a little.
649 var extraPercent
= 0.25;
651 yMax
= Dygraph
.log10(yMax
);
652 yMax
+= yMax
*extraPercent
;
653 yMin
= Dygraph
.log10(yMin
);
654 for (i
= 0; i
< combinedSeries
.length
; i
++) {
655 combinedSeries
[i
][1] = Dygraph
.log10(combinedSeries
[i
][1]);
659 var yRange
= yMax
- yMin
;
660 if (yRange
<= Number
.MIN_VALUE
) {
661 yExtra
= yMax
*extraPercent
;
663 yExtra
= yRange
*extraPercent
;
669 return {data
: combinedSeries
, yMin
: yMin
, yMax
: yMax
};
674 * Places the zoom handles in the proper position based on the current X data window.
676 DygraphRangeSelector
.prototype.placeZoomHandles_
= function() {
677 var xExtremes
= this.dygraph_
.xAxisExtremes();
678 var xWindowLimits
= this.dygraph_
.xAxisRange();
679 var xRange
= xExtremes
[1] - xExtremes
[0];
680 var leftPercent
= Math
.max(0, (xWindowLimits
[0] - xExtremes
[0])/xRange
);
681 var rightPercent
= Math
.max(0, (xExtremes
[1] - xWindowLimits
[1])/xRange
);
682 var leftCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*leftPercent
;
683 var rightCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*(1 - rightPercent
);
684 var handleTop
= Math
.max(this.canvasRect_
.y
, this.canvasRect_
.y
+ (this.canvasRect_
.h
- this.leftZoomHandle_
.height
)/2);
685 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
686 this.leftZoomHandle_
.style
.left
= (leftCoord
- halfHandleWidth
) + 'px';
687 this.leftZoomHandle_
.style
.top
= handleTop
+ 'px';
688 this.rightZoomHandle_
.style
.left
= (rightCoord
- halfHandleWidth
) + 'px';
689 this.rightZoomHandle_
.style
.top
= this.leftZoomHandle_
.style
.top
;
691 this.leftZoomHandle_
.style
.visibility
= 'visible';
692 this.rightZoomHandle_
.style
.visibility
= 'visible';
697 * Draws the interactive layer in the foreground canvas.
699 DygraphRangeSelector
.prototype.drawInteractiveLayer_
= function() {
700 var ctx
= this.fgcanvas_ctx_
;
701 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
703 var width
= this.canvasRect_
.w
- margin
;
704 var height
= this.canvasRect_
.h
- margin
;
705 var zoomHandleStatus
= this.getZoomHandleStatus_();
707 ctx
.strokeStyle
= 'black';
708 if (!zoomHandleStatus
.isZoomed
) {
710 ctx
.moveTo(margin
, margin
);
711 ctx
.lineTo(margin
, height
);
712 ctx
.lineTo(width
, height
);
713 ctx
.lineTo(width
, margin
);
715 if (this.iePanOverlay_
) {
716 this.iePanOverlay_
.style
.display
= 'none';
719 var leftHandleCanvasPos
= Math
.max(margin
, zoomHandleStatus
.leftHandlePos
- this.canvasRect_
.x
);
720 var rightHandleCanvasPos
= Math
.min(width
, zoomHandleStatus
.rightHandlePos
- this.canvasRect_
.x
);
722 ctx
.fillStyle
= 'rgba(240, 240, 240, 0.6)';
723 ctx
.fillRect(0, 0, leftHandleCanvasPos
, this.canvasRect_
.h
);
724 ctx
.fillRect(rightHandleCanvasPos
, 0, this.canvasRect_
.w
- rightHandleCanvasPos
, this.canvasRect_
.h
);
727 ctx
.moveTo(margin
, margin
);
728 ctx
.lineTo(leftHandleCanvasPos
, margin
);
729 ctx
.lineTo(leftHandleCanvasPos
, height
);
730 ctx
.lineTo(rightHandleCanvasPos
, height
);
731 ctx
.lineTo(rightHandleCanvasPos
, margin
);
732 ctx
.lineTo(width
, margin
);
735 if (this.isUsingExcanvas_
) {
736 this.iePanOverlay_
.style
.width
= (rightHandleCanvasPos
- leftHandleCanvasPos
) + 'px';
737 this.iePanOverlay_
.style
.left
= leftHandleCanvasPos
+ 'px';
738 this.iePanOverlay_
.style
.height
= height
+ 'px';
739 this.iePanOverlay_
.style
.display
= 'inline';
746 * Returns the current zoom handle position information.
747 * @return {Object} The zoom handle status.
749 DygraphRangeSelector
.prototype.getZoomHandleStatus_
= function() {
750 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
751 var leftHandlePos
= parseInt(this.leftZoomHandle_
.style
.left
, 10) + halfHandleWidth
;
752 var rightHandlePos
= parseInt(this.rightZoomHandle_
.style
.left
, 10) + halfHandleWidth
;
754 leftHandlePos
: leftHandlePos
,
755 rightHandlePos
: rightHandlePos
,
756 isZoomed
: (leftHandlePos
- 1 > this.canvasRect_
.x
|| rightHandlePos
+ 1 < this.canvasRect_
.x
+this.canvasRect_
.w
)