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 // Range selector status values
20 var NOT_CREATED
= 0; // Range selector interface has not been created
21 var CREATED
= 1 << 0; // Range selector interface has been created
22 var ADDED_TO_GRAPH
= 1 << 1; // Range selector elements have been added to the graph
24 var rangeSelector
= function() {
25 this.isIE_
= /MSIE/.test(navigator
.userAgent
) && !window
.opera
;
26 this.hasTouchInterface_
= typeof(TouchEvent
) != 'undefined';
27 this.isMobileDevice_
= /mobile|android/gi.test(navigator
.appVersion
);
28 this.status_
= NOT_CREATED
;
31 rangeSelector
.prototype.toString
= function() {
32 return "RangeSelector Plugin";
35 rangeSelector
.prototype.activate
= function(dygraph
) {
36 this.dygraph_
= dygraph
;
37 this.layout_
= dygraph
.layout_
;
38 this.graphDiv_
= dygraph
.graphDiv
;
39 this.isUsingExcanvas_
= dygraph
.isUsingExcanvas_
;
40 if (this.getOption_('showRangeSelector')) {
41 this.createInterface_();
44 layout
: this.reserveSpace_
,
45 predraw
: this.renderStaticLayer_
,
46 didDrawChart
: this.renderInteractiveLayer_
50 rangeSelector
.prototype.destroy
= function() {
51 this.bgcanvas_
= null;
52 this.fgcanvas_
= null;
53 this.leftZoomHandle_
= null;
54 this.rightZoomHandle_
= null;
55 this.iePanOverlay_
= null;
58 //------------------------------------------------------------------
60 //------------------------------------------------------------------
62 rangeSelector
.prototype.getOption_
= function(name
) {
63 return this.dygraph_
.getOption(name
);
66 rangeSelector
.prototype.setDefaultOption_
= function(name
, value
) {
67 return this.dygraph_
.attrs_
[name
] = value
;
72 * Creates the range selector elements and adds them to the graph.
74 rangeSelector
.prototype.createInterface_
= function() {
75 this.createCanvases_();
76 if (this.isUsingExcanvas_
) {
77 this.createIEPanOverlay_();
79 this.createZoomHandles_();
80 this.initInteraction_();
82 // Range selector and animatedZooms have a bad interaction. See issue 359.
83 if (this.getOption_('animatedZooms')) {
84 this.dygraph_
.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.');
85 this.dygraph_
.updateOptions({animatedZooms
: false}, true);
89 this.status_
= CREATED
;
94 * Adds the range selector to the graph.
96 rangeSelector
.prototype.addToGraph_
= function() {
97 var graphDiv
= this.graphDiv_
;
98 graphDiv
.appendChild(this.bgcanvas_
);
99 graphDiv
.appendChild(this.fgcanvas_
);
100 graphDiv
.appendChild(this.leftZoomHandle_
);
101 graphDiv
.appendChild(this.rightZoomHandle_
);
102 this.status_
|= ADDED_TO_GRAPH
;
107 * Removes the range selector from the graph.
109 rangeSelector
.prototype.removeFromGraph_
= function() {
110 var graphDiv
= this.graphDiv_
;
111 graphDiv
.removeChild(this.bgcanvas_
);
112 graphDiv
.removeChild(this.fgcanvas_
);
113 graphDiv
.removeChild(this.leftZoomHandle_
);
114 graphDiv
.removeChild(this.rightZoomHandle_
);
115 this.status_
^= ADDED_TO_GRAPH
;
120 * Called by Layout to allow range selector to reserve its space.
122 rangeSelector
.prototype.reserveSpace_
= function(e
) {
123 if (this.getOption_('showRangeSelector')) {
124 e
.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4);
130 * Renders the static portion of the range selector at the predraw stage.
132 rangeSelector
.prototype.renderStaticLayer_
= function() {
133 if (!this.updateInterfaceStatus_()) {
137 this.drawStaticLayer_();
142 * Renders the interactive portion of the range selector after the chart has been drawn.
144 rangeSelector
.prototype.renderInteractiveLayer_
= function() {
145 if (!this.updateInterfaceStatus_() || this.isChangingRange_
) {
148 this.placeZoomHandles_();
149 this.drawInteractiveLayer_();
154 * Check to see if the range selector is enabled/disabled and update interface accordingly.
156 rangeSelector
.prototype.updateInterfaceStatus_
= function() {
157 var enabled
= this.getOption_('showRangeSelector');
159 if (!(this.status_
& CREATED
)) {
160 this.createInterface_();
161 } else if (!(this.status_
& ADDED_TO_GRAPH
)) {
164 } else if (this.status_
& ADDED_TO_GRAPH
) {
165 this.removeFromGraph_();
166 var dygraph
= this.dygraph_
;
167 setTimeout(function() { dygraph
.width_
= 0; dygraph
.resize(); }, 1);
174 * Resizes the range selector.
176 rangeSelector
.prototype.resize_
= function() {
177 function setElementRect(canvas
, rect
) {
178 canvas
.style
.top
= rect
.y
+ 'px';
179 canvas
.style
.left
= rect
.x
+ 'px';
180 canvas
.width
= rect
.w
;
181 canvas
.height
= rect
.h
;
182 canvas
.style
.width
= canvas
.width
+ 'px'; // for IE
183 canvas
.style
.height
= canvas
.height
+ 'px'; // for IE
186 var plotArea
= this.layout_
.getPlotArea();
187 var xAxisLabelHeight
= this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'));
190 y
: plotArea
.y
+ plotArea
.h
+ xAxisLabelHeight
+ 4,
192 h
: this.getOption_('rangeSelectorHeight')
195 setElementRect(this.bgcanvas_
, this.canvasRect_
);
196 setElementRect(this.fgcanvas_
, this.canvasRect_
);
201 * Creates the background and foreground canvases.
203 rangeSelector
.prototype.createCanvases_
= function() {
204 this.bgcanvas_
= Dygraph
.createCanvas();
205 this.bgcanvas_
.className
= 'dygraph-rangesel-bgcanvas';
206 this.bgcanvas_
.style
.position
= 'absolute';
207 this.bgcanvas_
.style
.zIndex
= 9;
208 this.bgcanvas_ctx_
= Dygraph
.getContext(this.bgcanvas_
);
210 this.fgcanvas_
= Dygraph
.createCanvas();
211 this.fgcanvas_
.className
= 'dygraph-rangesel-fgcanvas';
212 this.fgcanvas_
.style
.position
= 'absolute';
213 this.fgcanvas_
.style
.zIndex
= 9;
214 this.fgcanvas_
.style
.cursor
= 'default';
215 this.fgcanvas_ctx_
= Dygraph
.getContext(this.fgcanvas_
);
220 * Creates overlay divs for IE/Excanvas so that mouse events are handled properly.
222 rangeSelector
.prototype.createIEPanOverlay_
= function() {
223 this.iePanOverlay_
= document
.createElement("div");
224 this.iePanOverlay_
.style
.position
= 'absolute';
225 this.iePanOverlay_
.style
.backgroundColor
= 'white';
226 this.iePanOverlay_
.style
.filter
= 'alpha(opacity=0)';
227 this.iePanOverlay_
.style
.display
= 'none';
228 this.iePanOverlay_
.style
.cursor
= 'move';
229 this.fgcanvas_
.appendChild(this.iePanOverlay_
);
234 * Creates the zoom handle elements.
236 rangeSelector
.prototype.createZoomHandles_
= function() {
237 var img
= new Image();
238 img
.className
= 'dygraph-rangesel-zoomhandle';
239 img
.style
.position
= 'absolute';
240 img
.style
.zIndex
= 10;
241 img
.style
.visibility
= 'hidden'; // Initially hidden so they don't show up in the wrong place.
242 img
.style
.cursor
= 'col-resize';
244 if (/MSIE 7/.test(navigator
.userAgent
)) { // IE7 doesn't support embedded src data.
247 img
.style
.backgroundColor
= 'white';
248 img
.style
.border
= '1px solid #333333'; // Just show box in IE7.
252 img
.src
= 'data:image/png;base64,' +
253 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' +
254 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' +
255 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' +
256 '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' +
257 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=';
260 if (this.isMobileDevice_
) {
265 this.leftZoomHandle_
= img
;
266 this.rightZoomHandle_
= img
.cloneNode(false);
271 * Sets up the interaction for the range selector.
273 rangeSelector
.prototype.initInteraction_
= function() {
275 var topElem
= this.isIE_
? document
: window
;
278 var isZooming
= false;
279 var isPanning
= false;
280 var dynamic
= !this.isMobileDevice_
&& !this.isUsingExcanvas_
;
282 // We cover iframes during mouse interactions. See comments in
283 // dygraph-utils.js for more info on why this is a good idea.
284 var tarp
= new Dygraph
.IFrameTarp();
286 // functions, defined below. Defining them this way (rather than with
287 // "function foo() {...}" makes JSHint happy.
288 var toXDataWindow
, onZoomStart
, onZoom
, onZoomEnd
, doZoom
, isMouseInPanZone
,
289 onPanStart
, onPan
, onPanEnd
, doPan
, onCanvasMouseMove
, applyBrowserZoomLevel
;
291 // Touch event functions
292 var onZoomHandleTouchEvent
, onCanvasTouchEvent
, addTouchEvents
;
294 toXDataWindow
= function(zoomHandleStatus
) {
295 var xDataLimits
= self
.dygraph_
.xAxisExtremes();
296 var fact
= (xDataLimits
[1] - xDataLimits
[0])/self
.canvasRect_
.w
;
297 var xDataMin
= xDataLimits
[0] + (zoomHandleStatus
.leftHandlePos
- self
.canvasRect_
.x
)*fact
;
298 var xDataMax
= xDataLimits
[0] + (zoomHandleStatus
.rightHandlePos
- self
.canvasRect_
.x
)*fact
;
299 return [xDataMin
, xDataMax
];
302 applyBrowserZoomLevel
= function(delX
) {
303 var zoom
= window
.outerWidth
/document
.documentElement
.clientWidth
;
311 onZoomStart
= function(e
) {
312 Dygraph
.cancelEvent(e
);
315 handle
= e
.target
? e
.target
: e
.srcElement
;
316 self
.dygraph_
.addEvent(topElem
, 'mousemove', onZoom
);
317 self
.dygraph_
.addEvent(topElem
, 'mouseup', onZoomEnd
);
318 self
.fgcanvas_
.style
.cursor
= 'col-resize';
323 onZoom
= function(e
) {
327 Dygraph
.cancelEvent(e
);
328 var delX
= e
.screenX
- xLast
;
329 if (Math
.abs(delX
) < 4 || e
.screenX
=== 0) {
330 // First iPad move event seems to have screenX = 0
334 delX
= applyBrowserZoomLevel(delX
);
337 var zoomHandleStatus
= self
.getZoomHandleStatus_();
339 if (handle
== self
.leftZoomHandle_
) {
340 newPos
= zoomHandleStatus
.leftHandlePos
+ delX
;
341 newPos
= Math
.min(newPos
, zoomHandleStatus
.rightHandlePos
- handle
.width
- 3);
342 newPos
= Math
.max(newPos
, self
.canvasRect_
.x
);
344 newPos
= zoomHandleStatus
.rightHandlePos
+ delX
;
345 newPos
= Math
.min(newPos
, self
.canvasRect_
.x
+ self
.canvasRect_
.w
);
346 newPos
= Math
.max(newPos
, zoomHandleStatus
.leftHandlePos
+ handle
.width
+ 3);
348 var halfHandleWidth
= handle
.width
/2;
349 handle
.style
.left
= (newPos
- halfHandleWidth
) + 'px';
350 self
.drawInteractiveLayer_();
352 // Zoom on the fly (if not using excanvas).
359 onZoomEnd
= function(e
) {
365 Dygraph
.removeEvent(topElem
, 'mousemove', onZoom
);
366 Dygraph
.removeEvent(topElem
, 'mouseup', onZoomEnd
);
367 self
.fgcanvas_
.style
.cursor
= 'default';
369 // If using excanvas, Zoom now.
376 doZoom
= function() {
378 var zoomHandleStatus
= self
.getZoomHandleStatus_();
379 self
.isChangingRange_
= true;
380 if (!zoomHandleStatus
.isZoomed
) {
381 self
.dygraph_
.resetZoom();
383 var xDataWindow
= toXDataWindow(zoomHandleStatus
);
384 self
.dygraph_
.doZoomXDates_(xDataWindow
[0], xDataWindow
[1]);
387 self
.isChangingRange_
= false;
391 isMouseInPanZone
= function(e
) {
392 if (self
.isUsingExcanvas_
) {
393 return e
.srcElement
== self
.iePanOverlay_
;
395 var rect
= self
.leftZoomHandle_
.getBoundingClientRect();
396 var leftHandleClientX
= rect
.left
+ rect
.width
/2;
397 rect
= self
.rightZoomHandle_
.getBoundingClientRect();
398 var rightHandleClientX
= rect
.left
+ rect
.width
/2;
399 return (e
.clientX
> leftHandleClientX
&& e
.clientX
< rightHandleClientX
);
403 onPanStart
= function(e
) {
404 if (!isPanning
&& isMouseInPanZone(e
) && self
.getZoomHandleStatus_().isZoomed
) {
405 Dygraph
.cancelEvent(e
);
408 self
.dygraph_
.addEvent(topElem
, 'mousemove', onPan
);
409 self
.dygraph_
.addEvent(topElem
, 'mouseup', onPanEnd
);
415 onPan
= function(e
) {
419 Dygraph
.cancelEvent(e
);
421 var delX
= e
.screenX
- xLast
;
422 if (Math
.abs(delX
) < 4) {
426 delX
= applyBrowserZoomLevel(delX
);
429 var zoomHandleStatus
= self
.getZoomHandleStatus_();
430 var leftHandlePos
= zoomHandleStatus
.leftHandlePos
;
431 var rightHandlePos
= zoomHandleStatus
.rightHandlePos
;
432 var rangeSize
= rightHandlePos
- leftHandlePos
;
433 if (leftHandlePos
+ delX
<= self
.canvasRect_
.x
) {
434 leftHandlePos
= self
.canvasRect_
.x
;
435 rightHandlePos
= leftHandlePos
+ rangeSize
;
436 } else if (rightHandlePos
+ delX
>= self
.canvasRect_
.x
+ self
.canvasRect_
.w
) {
437 rightHandlePos
= self
.canvasRect_
.x
+ self
.canvasRect_
.w
;
438 leftHandlePos
= rightHandlePos
- rangeSize
;
440 leftHandlePos
+= delX
;
441 rightHandlePos
+= delX
;
443 var halfHandleWidth
= self
.leftZoomHandle_
.width
/2;
444 self
.leftZoomHandle_
.style
.left
= (leftHandlePos
- halfHandleWidth
) + 'px';
445 self
.rightZoomHandle_
.style
.left
= (rightHandlePos
- halfHandleWidth
) + 'px';
446 self
.drawInteractiveLayer_();
448 // Do pan on the fly (if not using excanvas).
455 onPanEnd
= function(e
) {
460 Dygraph
.removeEvent(topElem
, 'mousemove', onPan
);
461 Dygraph
.removeEvent(topElem
, 'mouseup', onPanEnd
);
462 // If using excanvas, do pan now.
471 self
.isChangingRange_
= true;
472 self
.dygraph_
.dateWindow_
= toXDataWindow(self
.getZoomHandleStatus_());
473 self
.dygraph_
.drawGraph_(false);
475 self
.isChangingRange_
= false;
479 onCanvasMouseMove
= function(e
) {
480 if (isZooming
|| isPanning
) {
483 var cursor
= isMouseInPanZone(e
) ? 'move' : 'default';
484 if (cursor
!= self
.fgcanvas_
.style
.cursor
) {
485 self
.fgcanvas_
.style
.cursor
= cursor
;
489 onZoomHandleTouchEvent
= function(e
) {
490 if (e
.type
== 'touchstart' && e
.targetTouches
.length
== 1) {
491 if (onZoomStart(e
.targetTouches
[0])) {
492 Dygraph
.cancelEvent(e
);
494 } else if (e
.type
== 'touchmove' && e
.targetTouches
.length
== 1) {
495 if (onZoom(e
.targetTouches
[0])) {
496 Dygraph
.cancelEvent(e
);
503 onCanvasTouchEvent
= function(e
) {
504 if (e
.type
== 'touchstart' && e
.targetTouches
.length
== 1) {
505 if (onPanStart(e
.targetTouches
[0])) {
506 Dygraph
.cancelEvent(e
);
508 } else if (e
.type
== 'touchmove' && e
.targetTouches
.length
== 1) {
509 if (onPan(e
.targetTouches
[0])) {
510 Dygraph
.cancelEvent(e
);
517 addTouchEvents
= function(elem
, fn
) {
518 var types
= ['touchstart', 'touchend', 'touchmove', 'touchcancel'];
519 for (var i
= 0; i
< types
.length
; i
++) {
520 self
.dygraph_
.addEvent(elem
, types
[i
], fn
);
524 this.setDefaultOption_('interactionModel', Dygraph
.Interaction
.dragIsPanInteractionModel
);
525 this.setDefaultOption_('panEdgeFraction', 0.0001);
527 var dragStartEvent
= window
.opera
? 'mousedown' : 'dragstart';
528 this.dygraph_
.addEvent(this.leftZoomHandle_
, dragStartEvent
, onZoomStart
);
529 this.dygraph_
.addEvent(this.rightZoomHandle_
, dragStartEvent
, onZoomStart
);
531 if (this.isUsingExcanvas_
) {
532 this.dygraph_
.addEvent(this.iePanOverlay_
, 'mousedown', onPanStart
);
534 this.dygraph_
.addEvent(this.fgcanvas_
, 'mousedown', onPanStart
);
535 this.dygraph_
.addEvent(this.fgcanvas_
, 'mousemove', onCanvasMouseMove
);
539 if (this.hasTouchInterface_
) {
540 addTouchEvents(this.leftZoomHandle_
, onZoomHandleTouchEvent
);
541 addTouchEvents(this.rightZoomHandle_
, onZoomHandleTouchEvent
);
542 addTouchEvents(this.fgcanvas_
, onCanvasTouchEvent
);
548 * Draws the static layer in the background canvas.
550 rangeSelector
.prototype.drawStaticLayer_
= function() {
551 var ctx
= this.bgcanvas_ctx_
;
552 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
554 this.drawMiniPlot_();
560 this.bgcanvas_ctx_
.lineWidth
= 1;
561 ctx
.strokeStyle
= 'gray';
563 ctx
.moveTo(margin
, margin
);
564 ctx
.lineTo(margin
, this.canvasRect_
.h
-margin
);
565 ctx
.lineTo(this.canvasRect_
.w
-margin
, this.canvasRect_
.h
-margin
);
566 ctx
.lineTo(this.canvasRect_
.w
-margin
, margin
);
573 * Draws the mini plot in the background canvas.
575 rangeSelector
.prototype.drawMiniPlot_
= function() {
576 var fillStyle
= this.getOption_('rangeSelectorPlotFillColor');
577 var strokeStyle
= this.getOption_('rangeSelectorPlotStrokeColor');
578 if (!fillStyle
&& !strokeStyle
) {
582 var stepPlot
= this.getOption_('stepPlot');
584 var combinedSeriesData
= this.computeCombinedSeriesAndLimits_();
585 var yRange
= combinedSeriesData
.yMax
- combinedSeriesData
.yMin
;
587 // Draw the mini plot.
588 var ctx
= this.bgcanvas_ctx_
;
591 var xExtremes
= this.dygraph_
.xAxisExtremes();
592 var xRange
= Math
.max(xExtremes
[1] - xExtremes
[0], 1.e
-30);
593 var xFact
= (this.canvasRect_
.w
- margin
)/xRange
;
594 var yFact
= (this.canvasRect_
.h
- margin
)/yRange
;
595 var canvasWidth
= this.canvasRect_
.w
- margin
;
596 var canvasHeight
= this.canvasRect_
.h
- margin
;
598 var prevX
= null, prevY
= null;
601 ctx
.moveTo(margin
, canvasHeight
);
602 for (var i
= 0; i
< combinedSeriesData
.data
.length
; i
++) {
603 var dataPoint
= combinedSeriesData
.data
[i
];
604 var x
= ((dataPoint
[0] !== null) ? ((dataPoint
[0] - xExtremes
[0])*xFact
) : NaN
);
605 var y
= ((dataPoint
[1] !== null) ? (canvasHeight
- (dataPoint
[1] - combinedSeriesData
.yMin
)*yFact
) : NaN
);
606 if (isFinite(x
) && isFinite(y
)) {
608 ctx
.lineTo(x
, canvasHeight
);
611 ctx
.lineTo(x
, prevY
);
620 ctx
.lineTo(x
, prevY
);
621 ctx
.lineTo(x
, canvasHeight
);
624 ctx
.lineTo(prevX
, canvasHeight
);
627 prevX
= prevY
= null;
630 ctx
.lineTo(canvasWidth
, canvasHeight
);
634 var lingrad
= this.bgcanvas_ctx_
.createLinearGradient(0, 0, 0, canvasHeight
);
635 lingrad
.addColorStop(0, 'white');
636 lingrad
.addColorStop(1, fillStyle
);
637 this.bgcanvas_ctx_
.fillStyle
= lingrad
;
642 this.bgcanvas_ctx_
.strokeStyle
= strokeStyle
;
643 this.bgcanvas_ctx_
.lineWidth
= 1.5;
650 * Computes and returns the combinded series data along with min/max for the mini plot.
651 * @return {Object} An object containing combinded series array, ymin, ymax.
653 rangeSelector
.prototype.computeCombinedSeriesAndLimits_
= function() {
654 var data
= this.dygraph_
.rawData_
;
655 var logscale
= this.getOption_('logscale');
657 // Create a combined series (average of all series values).
658 var combinedSeries
= [];
665 // Find out if data has multiple values per datapoint.
666 // Go to first data point that actually has values (see http://code.google.com/p/dygraphs/issues/detail
?id
=246)
667 for (i
= 0; i
< data
.length
; i
++) {
668 if (data
[i
].length
> 1 && data
[i
][1] !== null) {
669 mutipleValues
= typeof data
[i
][1] != 'number';
673 for (k
= 0; k
< data
[i
][1].length
; k
++) {
682 for (i
= 0; i
< data
.length
; i
++) {
683 var dataPoint
= data
[i
];
687 for (k
= 0; k
< sum
.length
; k
++) {
688 sum
[k
] = count
[k
] = 0;
694 for (j
= 1; j
< dataPoint
.length
; j
++) {
695 if (this.dygraph_
.visibility()[j
-1]) {
698 for (k
= 0; k
< sum
.length
; k
++) {
700 if (y
=== null || isNaN(y
)) continue;
706 if (y
=== null || isNaN(y
)) continue;
714 for (k
= 0; k
< sum
.length
; k
++) {
722 combinedSeries
.push([xVal
, yVal
]);
725 // Account for roll period, fractions.
726 combinedSeries
= this.dygraph_
.rollingAverage(combinedSeries
, this.dygraph_
.rollPeriod_
);
728 if (typeof combinedSeries
[0][1] != 'number') {
729 for (i
= 0; i
< combinedSeries
.length
; i
++) {
730 yVal
= combinedSeries
[i
][1];
731 combinedSeries
[i
][1] = yVal
[0];
735 // Compute the y range.
736 var yMin
= Number
.MAX_VALUE
;
737 var yMax
= -Number
.MAX_VALUE
;
738 for (i
= 0; i
< combinedSeries
.length
; i
++) {
739 yVal
= combinedSeries
[i
][1];
740 if (yVal
!== null && isFinite(yVal
) && (!logscale
|| yVal
> 0)) {
741 yMin
= Math
.min(yMin
, yVal
);
742 yMax
= Math
.max(yMax
, yVal
);
746 // Convert Y data to log scale if needed.
747 // Also, expand the Y range to compress the mini plot a little.
748 var extraPercent
= 0.25;
750 yMax
= Dygraph
.log10(yMax
);
751 yMax
+= yMax
*extraPercent
;
752 yMin
= Dygraph
.log10(yMin
);
753 for (i
= 0; i
< combinedSeries
.length
; i
++) {
754 combinedSeries
[i
][1] = Dygraph
.log10(combinedSeries
[i
][1]);
758 var yRange
= yMax
- yMin
;
759 if (yRange
<= Number
.MIN_VALUE
) {
760 yExtra
= yMax
*extraPercent
;
762 yExtra
= yRange
*extraPercent
;
768 return {data
: combinedSeries
, yMin
: yMin
, yMax
: yMax
};
773 * Places the zoom handles in the proper position based on the current X data window.
775 rangeSelector
.prototype.placeZoomHandles_
= function() {
776 var xExtremes
= this.dygraph_
.xAxisExtremes();
777 var xWindowLimits
= this.dygraph_
.xAxisRange();
778 var xRange
= xExtremes
[1] - xExtremes
[0];
779 var leftPercent
= Math
.max(0, (xWindowLimits
[0] - xExtremes
[0])/xRange
);
780 var rightPercent
= Math
.max(0, (xExtremes
[1] - xWindowLimits
[1])/xRange
);
781 var leftCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*leftPercent
;
782 var rightCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*(1 - rightPercent
);
783 var handleTop
= Math
.max(this.canvasRect_
.y
, this.canvasRect_
.y
+ (this.canvasRect_
.h
- this.leftZoomHandle_
.height
)/2);
784 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
785 this.leftZoomHandle_
.style
.left
= (leftCoord
- halfHandleWidth
) + 'px';
786 this.leftZoomHandle_
.style
.top
= handleTop
+ 'px';
787 this.rightZoomHandle_
.style
.left
= (rightCoord
- halfHandleWidth
) + 'px';
788 this.rightZoomHandle_
.style
.top
= this.leftZoomHandle_
.style
.top
;
790 this.leftZoomHandle_
.style
.visibility
= 'visible';
791 this.rightZoomHandle_
.style
.visibility
= 'visible';
796 * Draws the interactive layer in the foreground canvas.
798 rangeSelector
.prototype.drawInteractiveLayer_
= function() {
799 var ctx
= this.fgcanvas_ctx_
;
800 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
802 var width
= this.canvasRect_
.w
- margin
;
803 var height
= this.canvasRect_
.h
- margin
;
804 var zoomHandleStatus
= this.getZoomHandleStatus_();
806 ctx
.strokeStyle
= 'black';
807 if (!zoomHandleStatus
.isZoomed
) {
809 ctx
.moveTo(margin
, margin
);
810 ctx
.lineTo(margin
, height
);
811 ctx
.lineTo(width
, height
);
812 ctx
.lineTo(width
, margin
);
814 if (this.iePanOverlay_
) {
815 this.iePanOverlay_
.style
.display
= 'none';
818 var leftHandleCanvasPos
= Math
.max(margin
, zoomHandleStatus
.leftHandlePos
- this.canvasRect_
.x
);
819 var rightHandleCanvasPos
= Math
.min(width
, zoomHandleStatus
.rightHandlePos
- this.canvasRect_
.x
);
821 ctx
.fillStyle
= 'rgba(240, 240, 240, 0.6)';
822 ctx
.fillRect(0, 0, leftHandleCanvasPos
, this.canvasRect_
.h
);
823 ctx
.fillRect(rightHandleCanvasPos
, 0, this.canvasRect_
.w
- rightHandleCanvasPos
, this.canvasRect_
.h
);
826 ctx
.moveTo(margin
, margin
);
827 ctx
.lineTo(leftHandleCanvasPos
, margin
);
828 ctx
.lineTo(leftHandleCanvasPos
, height
);
829 ctx
.lineTo(rightHandleCanvasPos
, height
);
830 ctx
.lineTo(rightHandleCanvasPos
, margin
);
831 ctx
.lineTo(width
, margin
);
834 if (this.isUsingExcanvas_
) {
835 this.iePanOverlay_
.style
.width
= (rightHandleCanvasPos
- leftHandleCanvasPos
) + 'px';
836 this.iePanOverlay_
.style
.left
= leftHandleCanvasPos
+ 'px';
837 this.iePanOverlay_
.style
.height
= height
+ 'px';
838 this.iePanOverlay_
.style
.display
= 'inline';
845 * Returns the current zoom handle position information.
846 * @return {Object} The zoom handle status.
848 rangeSelector
.prototype.getZoomHandleStatus_
= function() {
849 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
850 var leftHandlePos
= parseFloat(this.leftZoomHandle_
.style
.left
) + halfHandleWidth
;
851 var rightHandlePos
= parseFloat(this.rightZoomHandle_
.style
.left
) + halfHandleWidth
;
853 leftHandlePos
: leftHandlePos
,
854 rightHandlePos
: rightHandlePos
,
855 isZoomed
: (leftHandlePos
- 1 > this.canvasRect_
.x
|| rightHandlePos
+ 1 < this.canvasRect_
.x
+this.canvasRect_
.w
)
859 return rangeSelector
;