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 /*global Dygraph:false */
16 import * as utils from
'../dygraph-utils';
17 import DygraphInteraction from
'../dygraph-interaction-model';
18 import IFrameTarp from
'../iframe-tarp';
20 var rangeSelector
= function() {
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 if (this.getOption_('showRangeSelector')) {
33 this.createInterface_();
36 layout
: this.reserveSpace_
,
37 predraw
: this.renderStaticLayer_
,
38 didDrawChart
: this.renderInteractiveLayer_
42 rangeSelector
.prototype.destroy
= function() {
43 this.bgcanvas_
= null;
44 this.fgcanvas_
= null;
45 this.leftZoomHandle_
= null;
46 this.rightZoomHandle_
= null;
49 //------------------------------------------------------------------
51 //------------------------------------------------------------------
53 rangeSelector
.prototype.getOption_
= function(name
, opt_series
) {
54 return this.dygraph_
.getOption(name
, opt_series
);
57 rangeSelector
.prototype.setDefaultOption_
= function(name
, value
) {
58 this.dygraph_
.attrs_
[name
] = value
;
63 * Creates the range selector elements and adds them to the graph.
65 rangeSelector
.prototype.createInterface_
= function() {
66 this.createCanvases_();
67 this.createZoomHandles_();
68 this.initInteraction_();
70 // Range selector and animatedZooms have a bad interaction. See issue 359.
71 if (this.getOption_('animatedZooms')) {
72 console
.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.');
73 this.dygraph_
.updateOptions({animatedZooms
: false}, true);
76 this.interfaceCreated_
= true;
82 * Adds the range selector to the graph.
84 rangeSelector
.prototype.addToGraph_
= function() {
85 var graphDiv
= this.graphDiv_
= this.dygraph_
.graphDiv
;
86 graphDiv
.appendChild(this.bgcanvas_
);
87 graphDiv
.appendChild(this.fgcanvas_
);
88 graphDiv
.appendChild(this.leftZoomHandle_
);
89 graphDiv
.appendChild(this.rightZoomHandle_
);
94 * Removes the range selector from the graph.
96 rangeSelector
.prototype.removeFromGraph_
= function() {
97 var graphDiv
= this.graphDiv_
;
98 graphDiv
.removeChild(this.bgcanvas_
);
99 graphDiv
.removeChild(this.fgcanvas_
);
100 graphDiv
.removeChild(this.leftZoomHandle_
);
101 graphDiv
.removeChild(this.rightZoomHandle_
);
102 this.graphDiv_
= null;
107 * Called by Layout to allow range selector to reserve its space.
109 rangeSelector
.prototype.reserveSpace_
= function(e
) {
110 if (this.getOption_('showRangeSelector')) {
111 e
.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4);
117 * Renders the static portion of the range selector at the predraw stage.
119 rangeSelector
.prototype.renderStaticLayer_
= function() {
120 if (!this.updateVisibility_()) {
124 this.drawStaticLayer_();
129 * Renders the interactive portion of the range selector after the chart has been drawn.
131 rangeSelector
.prototype.renderInteractiveLayer_
= function() {
132 if (!this.updateVisibility_() || this.isChangingRange_
) {
135 this.placeZoomHandles_();
136 this.drawInteractiveLayer_();
141 * Check to see if the range selector is enabled/disabled and update visibility accordingly.
143 rangeSelector
.prototype.updateVisibility_
= function() {
144 var enabled
= this.getOption_('showRangeSelector');
146 if (!this.interfaceCreated_
) {
147 this.createInterface_();
148 } else if (!this.graphDiv_
|| !this.graphDiv_
.parentNode
) {
151 } else if (this.graphDiv_
) {
152 this.removeFromGraph_();
153 var dygraph
= this.dygraph_
;
154 setTimeout(function() { dygraph
.width_
= 0; dygraph
.resize(); }, 1);
161 * Resizes the range selector.
163 rangeSelector
.prototype.resize_
= function() {
164 function setElementRect(canvas
, context
, rect
, pixelRatioOption
) {
165 var canvasScale
= pixelRatioOption
|| utils
.getContextPixelRatio(context
);
167 canvas
.style
.top
= rect
.y
+ 'px';
168 canvas
.style
.left
= rect
.x
+ 'px';
169 canvas
.width
= rect
.w
* canvasScale
;
170 canvas
.height
= rect
.h
* canvasScale
;
171 canvas
.style
.width
= rect
.w
+ 'px';
172 canvas
.style
.height
= rect
.h
+ 'px';
174 if(canvasScale
!= 1) {
175 context
.scale(canvasScale
, canvasScale
);
179 var plotArea
= this.dygraph_
.layout_
.getPlotArea();
181 var xAxisLabelHeight
= 0;
182 if (this.dygraph_
.getOptionForAxis('drawAxis', 'x')) {
183 xAxisLabelHeight
= this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'));
187 y
: plotArea
.y
+ plotArea
.h
+ xAxisLabelHeight
+ 4,
189 h
: this.getOption_('rangeSelectorHeight')
192 var pixelRatioOption
= this.dygraph_
.getNumericOption('pixelRatio');
193 setElementRect(this.bgcanvas_
, this.bgcanvas_ctx_
, this.canvasRect_
, pixelRatioOption
);
194 setElementRect(this.fgcanvas_
, this.fgcanvas_ctx_
, this.canvasRect_
, pixelRatioOption
);
199 * Creates the background and foreground canvases.
201 rangeSelector
.prototype.createCanvases_
= function() {
202 this.bgcanvas_
= utils
.createCanvas();
203 this.bgcanvas_
.className
= 'dygraph-rangesel-bgcanvas';
204 this.bgcanvas_
.style
.position
= 'absolute';
205 this.bgcanvas_
.style
.zIndex
= 9;
206 this.bgcanvas_ctx_
= utils
.getContext(this.bgcanvas_
);
208 this.fgcanvas_
= utils
.createCanvas();
209 this.fgcanvas_
.className
= 'dygraph-rangesel-fgcanvas';
210 this.fgcanvas_
.style
.position
= 'absolute';
211 this.fgcanvas_
.style
.zIndex
= 9;
212 this.fgcanvas_
.style
.cursor
= 'default';
213 this.fgcanvas_ctx_
= utils
.getContext(this.fgcanvas_
);
218 * Creates the zoom handle elements.
220 rangeSelector
.prototype.createZoomHandles_
= function() {
221 var img
= new Image();
222 img
.className
= 'dygraph-rangesel-zoomhandle';
223 img
.style
.position
= 'absolute';
224 img
.style
.zIndex
= 10;
225 img
.style
.visibility
= 'hidden'; // Initially hidden so they don't show up in the wrong place.
226 img
.style
.cursor
= 'col-resize';
227 // TODO: change image to more options
230 img
.src
= 'data:image/png;base64,' +
231 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' +
232 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' +
233 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' +
234 '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' +
235 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=';
237 if (this.isMobileDevice_
) {
242 this.leftZoomHandle_
= img
;
243 this.rightZoomHandle_
= img
.cloneNode(false);
248 * Sets up the interaction for the range selector.
250 rangeSelector
.prototype.initInteraction_
= function() {
252 var topElem
= document
;
255 var isZooming
= false;
256 var isPanning
= false;
257 var dynamic
= !this.isMobileDevice_
;
259 // We cover iframes during mouse interactions. See comments in
260 // dygraph-utils.js for more info on why this is a good idea.
261 var tarp
= new IFrameTarp();
263 // functions, defined below. Defining them this way (rather than with
264 // "function foo() {...}" makes JSHint happy.
265 var toXDataWindow
, onZoomStart
, onZoom
, onZoomEnd
, doZoom
, isMouseInPanZone
,
266 onPanStart
, onPan
, onPanEnd
, doPan
, onCanvasHover
;
268 // Touch event functions
269 var onZoomHandleTouchEvent
, onCanvasTouchEvent
, addTouchEvents
;
271 toXDataWindow
= function(zoomHandleStatus
) {
272 var xDataLimits
= self
.dygraph_
.xAxisExtremes();
273 var fact
= (xDataLimits
[1] - xDataLimits
[0])/self
.canvasRect_
.w
;
274 var xDataMin
= xDataLimits
[0] + (zoomHandleStatus
.leftHandlePos
- self
.canvasRect_
.x
)*fact
;
275 var xDataMax
= xDataLimits
[0] + (zoomHandleStatus
.rightHandlePos
- self
.canvasRect_
.x
)*fact
;
276 return [xDataMin
, xDataMax
];
279 onZoomStart
= function(e
) {
280 utils
.cancelEvent(e
);
282 clientXLast
= e
.clientX
;
283 handle
= e
.target
? e
.target
: e
.srcElement
;
284 if (e
.type
=== 'mousedown' || e
.type
=== 'dragstart') {
285 // These events are removed manually.
286 utils
.addEvent(topElem
, 'mousemove', onZoom
);
287 utils
.addEvent(topElem
, 'mouseup', onZoomEnd
);
289 self
.fgcanvas_
.style
.cursor
= 'col-resize';
294 onZoom
= function(e
) {
298 utils
.cancelEvent(e
);
300 var delX
= e
.clientX
- clientXLast
;
301 if (Math
.abs(delX
) < 4) {
304 clientXLast
= e
.clientX
;
307 var zoomHandleStatus
= self
.getZoomHandleStatus_();
309 if (handle
== self
.leftZoomHandle_
) {
310 newPos
= zoomHandleStatus
.leftHandlePos
+ delX
;
311 newPos
= Math
.min(newPos
, zoomHandleStatus
.rightHandlePos
- handle
.width
- 3);
312 newPos
= Math
.max(newPos
, self
.canvasRect_
.x
);
314 newPos
= zoomHandleStatus
.rightHandlePos
+ delX
;
315 newPos
= Math
.min(newPos
, self
.canvasRect_
.x
+ self
.canvasRect_
.w
);
316 newPos
= Math
.max(newPos
, zoomHandleStatus
.leftHandlePos
+ handle
.width
+ 3);
318 var halfHandleWidth
= handle
.width
/2;
319 handle
.style
.left
= (newPos
- halfHandleWidth
) + 'px';
320 self
.drawInteractiveLayer_();
329 onZoomEnd
= function(e
) {
335 utils
.removeEvent(topElem
, 'mousemove', onZoom
);
336 utils
.removeEvent(topElem
, 'mouseup', onZoomEnd
);
337 self
.fgcanvas_
.style
.cursor
= 'default';
339 // If on a slower device, zoom now.
346 doZoom
= function() {
348 var zoomHandleStatus
= self
.getZoomHandleStatus_();
349 self
.isChangingRange_
= true;
350 if (!zoomHandleStatus
.isZoomed
) {
351 self
.dygraph_
.resetZoom();
353 var xDataWindow
= toXDataWindow(zoomHandleStatus
);
354 self
.dygraph_
.doZoomXDates_(xDataWindow
[0], xDataWindow
[1]);
357 self
.isChangingRange_
= false;
361 isMouseInPanZone
= function(e
) {
362 var rect
= self
.leftZoomHandle_
.getBoundingClientRect();
363 var leftHandleClientX
= rect
.left
+ rect
.width
/2;
364 rect
= self
.rightZoomHandle_
.getBoundingClientRect();
365 var rightHandleClientX
= rect
.left
+ rect
.width
/2;
366 return (e
.clientX
> leftHandleClientX
&& e
.clientX
< rightHandleClientX
);
369 onPanStart
= function(e
) {
370 if (!isPanning
&& isMouseInPanZone(e
) && self
.getZoomHandleStatus_().isZoomed
) {
371 utils
.cancelEvent(e
);
373 clientXLast
= e
.clientX
;
374 if (e
.type
=== 'mousedown') {
375 // These events are removed manually.
376 utils
.addEvent(topElem
, 'mousemove', onPan
);
377 utils
.addEvent(topElem
, 'mouseup', onPanEnd
);
384 onPan
= function(e
) {
388 utils
.cancelEvent(e
);
390 var delX
= e
.clientX
- clientXLast
;
391 if (Math
.abs(delX
) < 4) {
394 clientXLast
= e
.clientX
;
397 var zoomHandleStatus
= self
.getZoomHandleStatus_();
398 var leftHandlePos
= zoomHandleStatus
.leftHandlePos
;
399 var rightHandlePos
= zoomHandleStatus
.rightHandlePos
;
400 var rangeSize
= rightHandlePos
- leftHandlePos
;
401 if (leftHandlePos
+ delX
<= self
.canvasRect_
.x
) {
402 leftHandlePos
= self
.canvasRect_
.x
;
403 rightHandlePos
= leftHandlePos
+ rangeSize
;
404 } else if (rightHandlePos
+ delX
>= self
.canvasRect_
.x
+ self
.canvasRect_
.w
) {
405 rightHandlePos
= self
.canvasRect_
.x
+ self
.canvasRect_
.w
;
406 leftHandlePos
= rightHandlePos
- rangeSize
;
408 leftHandlePos
+= delX
;
409 rightHandlePos
+= delX
;
411 var halfHandleWidth
= self
.leftZoomHandle_
.width
/2;
412 self
.leftZoomHandle_
.style
.left
= (leftHandlePos
- halfHandleWidth
) + 'px';
413 self
.rightZoomHandle_
.style
.left
= (rightHandlePos
- halfHandleWidth
) + 'px';
414 self
.drawInteractiveLayer_();
416 // Do pan on the fly.
423 onPanEnd
= function(e
) {
428 utils
.removeEvent(topElem
, 'mousemove', onPan
);
429 utils
.removeEvent(topElem
, 'mouseup', onPanEnd
);
430 // If on a slower device, do pan now.
439 self
.isChangingRange_
= true;
440 self
.dygraph_
.dateWindow_
= toXDataWindow(self
.getZoomHandleStatus_());
441 self
.dygraph_
.drawGraph_(false);
443 self
.isChangingRange_
= false;
447 onCanvasHover
= function(e
) {
448 if (isZooming
|| isPanning
) {
451 var cursor
= isMouseInPanZone(e
) ? 'move' : 'default';
452 if (cursor
!= self
.fgcanvas_
.style
.cursor
) {
453 self
.fgcanvas_
.style
.cursor
= cursor
;
457 onZoomHandleTouchEvent
= function(e
) {
458 if (e
.type
== 'touchstart' && e
.targetTouches
.length
== 1) {
459 if (onZoomStart(e
.targetTouches
[0])) {
460 utils
.cancelEvent(e
);
462 } else if (e
.type
== 'touchmove' && e
.targetTouches
.length
== 1) {
463 if (onZoom(e
.targetTouches
[0])) {
464 utils
.cancelEvent(e
);
471 onCanvasTouchEvent
= function(e
) {
472 if (e
.type
== 'touchstart' && e
.targetTouches
.length
== 1) {
473 if (onPanStart(e
.targetTouches
[0])) {
474 utils
.cancelEvent(e
);
476 } else if (e
.type
== 'touchmove' && e
.targetTouches
.length
== 1) {
477 if (onPan(e
.targetTouches
[0])) {
478 utils
.cancelEvent(e
);
485 addTouchEvents
= function(elem
, fn
) {
486 var types
= ['touchstart', 'touchend', 'touchmove', 'touchcancel'];
487 for (var i
= 0; i
< types
.length
; i
++) {
488 self
.dygraph_
.addAndTrackEvent(elem
, types
[i
], fn
);
492 this.setDefaultOption_('interactionModel', DygraphInteraction
.dragIsPanInteractionModel
);
493 this.setDefaultOption_('panEdgeFraction', 0.0001);
495 var dragStartEvent
= window
.opera
? 'mousedown' : 'dragstart';
496 this.dygraph_
.addAndTrackEvent(this.leftZoomHandle_
, dragStartEvent
, onZoomStart
);
497 this.dygraph_
.addAndTrackEvent(this.rightZoomHandle_
, dragStartEvent
, onZoomStart
);
499 this.dygraph_
.addAndTrackEvent(this.fgcanvas_
, 'mousedown', onPanStart
);
500 this.dygraph_
.addAndTrackEvent(this.fgcanvas_
, 'mousemove', onCanvasHover
);
503 if (this.hasTouchInterface_
) {
504 addTouchEvents(this.leftZoomHandle_
, onZoomHandleTouchEvent
);
505 addTouchEvents(this.rightZoomHandle_
, onZoomHandleTouchEvent
);
506 addTouchEvents(this.fgcanvas_
, onCanvasTouchEvent
);
512 * Draws the static layer in the background canvas.
514 rangeSelector
.prototype.drawStaticLayer_
= function() {
515 var ctx
= this.bgcanvas_ctx_
;
516 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
518 this.drawMiniPlot_();
524 this.bgcanvas_ctx_
.lineWidth
= this.getOption_('rangeSelectorBackgroundLineWidth');
525 ctx
.strokeStyle
= this.getOption_('rangeSelectorBackgroundStrokeColor');
527 ctx
.moveTo(margin
, margin
);
528 ctx
.lineTo(margin
, this.canvasRect_
.h
-margin
);
529 ctx
.lineTo(this.canvasRect_
.w
-margin
, this.canvasRect_
.h
-margin
);
530 ctx
.lineTo(this.canvasRect_
.w
-margin
, margin
);
537 * Draws the mini plot in the background canvas.
539 rangeSelector
.prototype.drawMiniPlot_
= function() {
540 var fillStyle
= this.getOption_('rangeSelectorPlotFillColor');
541 var fillGradientStyle
= this.getOption_('rangeSelectorPlotFillGradientColor');
542 var strokeStyle
= this.getOption_('rangeSelectorPlotStrokeColor');
543 if (!fillStyle
&& !strokeStyle
) {
547 var stepPlot
= this.getOption_('stepPlot');
549 var combinedSeriesData
= this.computeCombinedSeriesAndLimits_();
550 var yRange
= combinedSeriesData
.yMax
- combinedSeriesData
.yMin
;
552 // Draw the mini plot.
553 var ctx
= this.bgcanvas_ctx_
;
556 var xExtremes
= this.dygraph_
.xAxisExtremes();
557 var xRange
= Math
.max(xExtremes
[1] - xExtremes
[0], 1.e
-30);
558 var xFact
= (this.canvasRect_
.w
- margin
)/xRange
;
559 var yFact
= (this.canvasRect_
.h
- margin
)/yRange
;
560 var canvasWidth
= this.canvasRect_
.w
- margin
;
561 var canvasHeight
= this.canvasRect_
.h
- margin
;
563 var prevX
= null, prevY
= null;
566 ctx
.moveTo(margin
, canvasHeight
);
567 for (var i
= 0; i
< combinedSeriesData
.data
.length
; i
++) {
568 var dataPoint
= combinedSeriesData
.data
[i
];
569 var x
= ((dataPoint
[0] !== null) ? ((dataPoint
[0] - xExtremes
[0])*xFact
) : NaN
);
570 var y
= ((dataPoint
[1] !== null) ? (canvasHeight
- (dataPoint
[1] - combinedSeriesData
.yMin
)*yFact
) : NaN
);
572 // Skip points that don't change the x-value. Overly fine-grained points
573 // can cause major slowdowns with the ctx.fill() call below.
574 if (!stepPlot
&& prevX
!== null && Math
.round(x
) == Math
.round(prevX
)) {
578 if (isFinite(x
) && isFinite(y
)) {
580 ctx
.lineTo(x
, canvasHeight
);
583 ctx
.lineTo(x
, prevY
);
592 ctx
.lineTo(x
, prevY
);
593 ctx
.lineTo(x
, canvasHeight
);
596 ctx
.lineTo(prevX
, canvasHeight
);
599 prevX
= prevY
= null;
602 ctx
.lineTo(canvasWidth
, canvasHeight
);
606 var lingrad
= this.bgcanvas_ctx_
.createLinearGradient(0, 0, 0, canvasHeight
);
607 if (fillGradientStyle
) {
608 lingrad
.addColorStop(0, fillGradientStyle
);
610 lingrad
.addColorStop(1, fillStyle
);
611 this.bgcanvas_ctx_
.fillStyle
= lingrad
;
616 this.bgcanvas_ctx_
.strokeStyle
= strokeStyle
;
617 this.bgcanvas_ctx_
.lineWidth
= this.getOption_('rangeSelectorPlotLineWidth');
624 * Computes and returns the combined series data along with min/max for the mini plot.
625 * The combined series consists of averaged values for all series.
626 * When series have error bars, the error bars are ignored.
627 * @return {Object} An object containing combined series array, ymin, ymax.
629 rangeSelector
.prototype.computeCombinedSeriesAndLimits_
= function() {
630 var g
= this.dygraph_
;
631 var logscale
= this.getOption_('logscale');
634 // Select series to combine. By default, all series are combined.
635 var numColumns
= g
.numColumns();
636 var labels
= g
.getLabels();
637 var includeSeries
= new Array(numColumns
);
639 var visibility
= g
.visibility();
642 for (i
= 1; i
< numColumns
; i
++) {
643 var include
= this.getOption_('showInRangeSelector', labels
[i
]);
644 inclusion
.push(include
);
645 if (include
!== null) anySet
= true; // it's set explicitly for this series
649 for (i
= 1; i
< numColumns
; i
++) {
650 includeSeries
[i
] = inclusion
[i
- 1];
653 for (i
= 1; i
< numColumns
; i
++) {
654 includeSeries
[i
] = visibility
[i
- 1];
658 // Create a combined series (average of selected series values).
659 // TODO(danvk): short-circuit if there's only one series.
660 var rolledSeries
= [];
661 var dataHandler
= g
.dataHandler_
;
662 var options
= g
.attributes_
;
663 for (i
= 1; i
< g
.numColumns(); i
++) {
664 if (!includeSeries
[i
]) continue;
665 var series
= dataHandler
.extractSeries(g
.rawData_
, i
, options
);
666 if (g
.rollPeriod() > 1) {
667 series
= dataHandler
.rollingAverage(series
, g
.rollPeriod(), options
);
670 rolledSeries
.push(series
);
673 var combinedSeries
= [];
674 for (i
= 0; i
< rolledSeries
[0].length
; i
++) {
677 for (var j
= 0; j
< rolledSeries
.length
; j
++) {
678 var y
= rolledSeries
[j
][i
][1];
679 if (y
=== null || isNaN(y
)) continue;
683 combinedSeries
.push([rolledSeries
[0][i
][0], sum
/ count
]);
686 // Compute the y range.
687 var yMin
= Number
.MAX_VALUE
;
688 var yMax
= -Number
.MAX_VALUE
;
689 for (i
= 0; i
< combinedSeries
.length
; i
++) {
690 var yVal
= combinedSeries
[i
][1];
691 if (yVal
!== null && isFinite(yVal
) && (!logscale
|| yVal
> 0)) {
692 yMin
= Math
.min(yMin
, yVal
);
693 yMax
= Math
.max(yMax
, yVal
);
697 // Convert Y data to log scale if needed.
698 // Also, expand the Y range to compress the mini plot a little.
699 var extraPercent
= 0.25;
701 yMax
= utils
.log10(yMax
);
702 yMax
+= yMax
*extraPercent
;
703 yMin
= utils
.log10(yMin
);
704 for (i
= 0; i
< combinedSeries
.length
; i
++) {
705 combinedSeries
[i
][1] = utils
.log10(combinedSeries
[i
][1]);
709 var yRange
= yMax
- yMin
;
710 if (yRange
<= Number
.MIN_VALUE
) {
711 yExtra
= yMax
*extraPercent
;
713 yExtra
= yRange
*extraPercent
;
719 return {data
: combinedSeries
, yMin
: yMin
, yMax
: yMax
};
724 * Places the zoom handles in the proper position based on the current X data window.
726 rangeSelector
.prototype.placeZoomHandles_
= function() {
727 var xExtremes
= this.dygraph_
.xAxisExtremes();
728 var xWindowLimits
= this.dygraph_
.xAxisRange();
729 var xRange
= xExtremes
[1] - xExtremes
[0];
730 var leftPercent
= Math
.max(0, (xWindowLimits
[0] - xExtremes
[0])/xRange
);
731 var rightPercent
= Math
.max(0, (xExtremes
[1] - xWindowLimits
[1])/xRange
);
732 var leftCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*leftPercent
;
733 var rightCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*(1 - rightPercent
);
734 var handleTop
= Math
.max(this.canvasRect_
.y
, this.canvasRect_
.y
+ (this.canvasRect_
.h
- this.leftZoomHandle_
.height
)/2);
735 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
736 this.leftZoomHandle_
.style
.left
= (leftCoord
- halfHandleWidth
) + 'px';
737 this.leftZoomHandle_
.style
.top
= handleTop
+ 'px';
738 this.rightZoomHandle_
.style
.left
= (rightCoord
- halfHandleWidth
) + 'px';
739 this.rightZoomHandle_
.style
.top
= this.leftZoomHandle_
.style
.top
;
741 this.leftZoomHandle_
.style
.visibility
= 'visible';
742 this.rightZoomHandle_
.style
.visibility
= 'visible';
747 * Draws the interactive layer in the foreground canvas.
749 rangeSelector
.prototype.drawInteractiveLayer_
= function() {
750 var ctx
= this.fgcanvas_ctx_
;
751 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
753 var width
= this.canvasRect_
.w
- margin
;
754 var height
= this.canvasRect_
.h
- margin
;
755 var zoomHandleStatus
= this.getZoomHandleStatus_();
757 ctx
.strokeStyle
= this.getOption_('rangeSelectorForegroundStrokeColor');
758 ctx
.lineWidth
= this.getOption_('rangeSelectorForegroundLineWidth');
759 if (!zoomHandleStatus
.isZoomed
) {
761 ctx
.moveTo(margin
, margin
);
762 ctx
.lineTo(margin
, height
);
763 ctx
.lineTo(width
, height
);
764 ctx
.lineTo(width
, margin
);
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, ' + this.getOption_('rangeSelectorAlpha').toString() + ')';
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
);
787 * Returns the current zoom handle position information.
788 * @return {Object} The zoom handle status.
790 rangeSelector
.prototype.getZoomHandleStatus_
= function() {
791 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
792 var leftHandlePos
= parseFloat(this.leftZoomHandle_
.style
.left
) + halfHandleWidth
;
793 var rightHandlePos
= parseFloat(this.rightZoomHandle_
.style
.left
) + halfHandleWidth
;
795 leftHandlePos
: leftHandlePos
,
796 rightHandlePos
: rightHandlePos
,
797 isZoomed
: (leftHandlePos
- 1 > this.canvasRect_
.x
|| rightHandlePos
+ 1 < this.canvasRect_
.x
+this.canvasRect_
.w
)
801 export default rangeSelector
;