1 // Copyright 2011 Paul Felix (paul.eric.felix@gmail.com)
2 // All Rights Reserved.
5 * @fileoverview This file contains the DygraphRangeSelector class used to provide
6 * a timeline range selector widget for dygraphs.
9 /*jshint globalstrict: true */
10 /*global Dygraph:false */
14 * The DygraphRangeSelector class provides a timeline range selector widget.
15 * @param {Dygraph} dygraph The dygraph object
18 var DygraphRangeSelector
= function(dygraph
) {
19 this.isIE_
= /MSIE/.test(navigator
.userAgent
) && !window
.opera
;
20 this.isUsingExcanvas_
= dygraph
.isUsingExcanvas_
;
21 this.dygraph_
= dygraph
;
22 this.createCanvases_();
23 if (this.isUsingExcanvas_
) {
24 this.createIEPanOverlay_();
26 this.createZoomHandles_();
27 this.initInteraction_();
31 * Adds the range selector to the dygraph.
32 * @param {Object} graphDiv The container div for the range selector.
33 * @param {DygraphLayout} layout The DygraphLayout object for this graph.
35 DygraphRangeSelector
.prototype.addToGraph
= function(graphDiv
, layout
) {
36 this.layout_
= layout
;
38 graphDiv
.appendChild(this.bgcanvas_
);
39 graphDiv
.appendChild(this.fgcanvas_
);
40 graphDiv
.appendChild(this.leftZoomHandle_
);
41 graphDiv
.appendChild(this.rightZoomHandle_
);
45 * Renders the static background portion of the range selector.
47 DygraphRangeSelector
.prototype.renderStaticLayer
= function() {
49 this.drawStaticLayer_();
53 * Renders the interactive foreground portion of the range selector.
55 DygraphRangeSelector
.prototype.renderInteractiveLayer
= function() {
56 if (this.isChangingRange_
) {
59 this.placeZoomHandles_();
60 this.drawInteractiveLayer_();
65 * Resizes the range selector.
67 DygraphRangeSelector
.prototype.resize_
= function() {
68 function setElementRect(canvas
, rect
) {
69 canvas
.style
.top
= rect
.y
+ 'px';
70 canvas
.style
.left
= rect
.x
+ 'px';
71 canvas
.width
= rect
.w
;
72 canvas
.height
= rect
.h
;
73 canvas
.style
.width
= canvas
.width
+ 'px'; // for IE
74 canvas
.style
.height
= canvas
.height
+ 'px'; // for IE
77 var plotArea
= this.layout_
.getPlotArea();
78 var xAxisLabelHeight
= this.attr_('axisLabelFontSize') + 2 * this.attr_('axisTickSize');
81 y
: plotArea
.y
+ plotArea
.h
+ xAxisLabelHeight
+ 4,
83 h
: this.attr_('rangeSelectorHeight')
86 setElementRect(this.bgcanvas_
, this.canvasRect_
);
87 setElementRect(this.fgcanvas_
, this.canvasRect_
);
90 DygraphRangeSelector
.prototype.attr_
= function(name
) {
91 return this.dygraph_
.attr_(name
);
96 * Creates the background and foreground canvases.
98 DygraphRangeSelector
.prototype.createCanvases_
= function() {
99 this.bgcanvas_
= Dygraph
.createCanvas();
100 this.bgcanvas_
.className
= 'dygraph-rangesel-bgcanvas';
101 this.bgcanvas_
.style
.position
= 'absolute';
102 this.bgcanvas_
.style
.zIndex
= 9;
103 this.bgcanvas_ctx_
= Dygraph
.getContext(this.bgcanvas_
);
105 this.fgcanvas_
= Dygraph
.createCanvas();
106 this.fgcanvas_
.className
= 'dygraph-rangesel-fgcanvas';
107 this.fgcanvas_
.style
.position
= 'absolute';
108 this.fgcanvas_
.style
.zIndex
= 9;
109 this.fgcanvas_
.style
.cursor
= 'default';
110 this.fgcanvas_ctx_
= Dygraph
.getContext(this.fgcanvas_
);
115 * Creates overlay divs for IE/Excanvas so that mouse events are handled properly.
117 DygraphRangeSelector
.prototype.createIEPanOverlay_
= function() {
118 this.iePanOverlay_
= document
.createElement("div");
119 this.iePanOverlay_
.style
.position
= 'absolute';
120 this.iePanOverlay_
.style
.backgroundColor
= 'white';
121 this.iePanOverlay_
.style
.filter
= 'alpha(opacity=0)';
122 this.iePanOverlay_
.style
.display
= 'none';
123 this.iePanOverlay_
.style
.cursor
= 'move';
124 this.fgcanvas_
.appendChild(this.iePanOverlay_
);
129 * Creates the zoom handle elements.
131 DygraphRangeSelector
.prototype.createZoomHandles_
= function() {
132 var img
= new Image();
133 img
.className
= 'dygraph-rangesel-zoomhandle';
134 img
.style
.position
= 'absolute';
135 img
.style
.zIndex
= 10;
136 img
.style
.visibility
= 'hidden'; // Initially hidden so they don't show up in the wrong place.
137 img
.style
.cursor
= 'col-resize';
138 if (/MSIE 7/.test(navigator
.userAgent
)) { // IE7 doesn't support embedded src data.
141 img
.style
.backgroundColor
= 'white';
142 img
.style
.border
= '1px solid #333333'; // Just show box in IE7.
146 img
.src
= 'data:image/png;base64,' +
147 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' +
148 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' +
149 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' +
150 '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' +
151 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=';
154 this.leftZoomHandle_
= img
;
155 this.rightZoomHandle_
= img
.cloneNode(false);
160 * Sets up the interaction for the range selector.
162 DygraphRangeSelector
.prototype.initInteraction_
= function() {
164 var topElem
= this.isIE_
? document
: window
;
167 var isZooming
= false;
168 var isPanning
= false;
170 // functions, defined below. Defining them this way (rather than with
171 // "function foo() {...}" makes JSHint happy.
172 var toXDataWindow
, onZoomStart
, onZoom
, onZoomEnd
, doZoom
, isMouseInPanZone
,
173 onPanStart
, onPan
, onPanEnd
, doPan
, onCanvasMouseMove
;
175 toXDataWindow
= function(zoomHandleStatus
) {
176 var xDataLimits
= self
.dygraph_
.xAxisExtremes();
177 var fact
= (xDataLimits
[1] - xDataLimits
[0])/self
.canvasRect_
.w
;
178 var xDataMin
= xDataLimits
[0] + (zoomHandleStatus
.leftHandlePos
- self
.canvasRect_
.x
)*fact
;
179 var xDataMax
= xDataLimits
[0] + (zoomHandleStatus
.rightHandlePos
- self
.canvasRect_
.x
)*fact
;
180 return [xDataMin
, xDataMax
];
183 onZoomStart
= function(e
) {
184 Dygraph
.cancelEvent(e
);
187 handle
= e
.target
? e
.target
: e
.srcElement
;
188 Dygraph
.addEvent(topElem
, 'mousemove', onZoom
);
189 Dygraph
.addEvent(topElem
, 'mouseup', onZoomEnd
);
190 self
.fgcanvas_
.style
.cursor
= 'col-resize';
193 onZoom
= function(e
) {
197 var delX
= e
.screenX
- xLast
;
198 if (Math
.abs(delX
) < 4) {
202 var zoomHandleStatus
= self
.getZoomHandleStatus_();
204 if (handle
== self
.leftZoomHandle_
) {
205 newPos
= zoomHandleStatus
.leftHandlePos
+ delX
;
206 newPos
= Math
.min(newPos
, zoomHandleStatus
.rightHandlePos
- handle
.width
- 3);
207 newPos
= Math
.max(newPos
, self
.canvasRect_
.x
);
209 newPos
= zoomHandleStatus
.rightHandlePos
+ delX
;
210 newPos
= Math
.min(newPos
, self
.canvasRect_
.x
+ self
.canvasRect_
.w
);
211 newPos
= Math
.max(newPos
, zoomHandleStatus
.leftHandlePos
+ handle
.width
+ 3);
213 var halfHandleWidth
= handle
.width
/2;
214 handle
.style
.left
= (newPos
- halfHandleWidth
) + 'px';
215 self
.drawInteractiveLayer_();
217 // Zoom on the fly (if not using excanvas).
218 if (!self
.isUsingExcanvas_
) {
223 onZoomEnd
= function(e
) {
228 Dygraph
.removeEvent(topElem
, 'mousemove', onZoom
);
229 Dygraph
.removeEvent(topElem
, 'mouseup', onZoomEnd
);
230 self
.fgcanvas_
.style
.cursor
= 'default';
232 // If using excanvas, Zoom now.
233 if (self
.isUsingExcanvas_
) {
238 doZoom
= function() {
240 var zoomHandleStatus
= self
.getZoomHandleStatus_();
241 self
.isChangingRange_
= true;
242 if (!zoomHandleStatus
.isZoomed
) {
243 self
.dygraph_
.doUnzoom_();
245 var xDataWindow
= toXDataWindow(zoomHandleStatus
);
246 self
.dygraph_
.doZoomXDates_(xDataWindow
[0], xDataWindow
[1]);
249 self
.isChangingRange_
= false;
253 isMouseInPanZone
= function(e
) {
254 if (self
.isUsingExcanvas_
) {
255 return e
.srcElement
== self
.iePanOverlay_
;
257 // Getting clientX directly from the event is not accurate enough :(
258 var clientX
= self
.canvasRect_
.x
+ (e
.layerX
!== undefined
? e
.layerX
: e
.offsetX
);
259 var zoomHandleStatus
= self
.getZoomHandleStatus_();
260 return (clientX
> zoomHandleStatus
.leftHandlePos
&& clientX
< zoomHandleStatus
.rightHandlePos
);
264 onPanStart
= function(e
) {
265 if (!isPanning
&& isMouseInPanZone(e
) && self
.getZoomHandleStatus_().isZoomed
) {
266 Dygraph
.cancelEvent(e
);
269 Dygraph
.addEvent(topElem
, 'mousemove', onPan
);
270 Dygraph
.addEvent(topElem
, 'mouseup', onPanEnd
);
274 onPan
= function(e
) {
278 Dygraph
.cancelEvent(e
);
280 var delX
= e
.screenX
- xLast
;
281 if (Math
.abs(delX
) < 4) {
287 var zoomHandleStatus
= self
.getZoomHandleStatus_();
288 var leftHandlePos
= zoomHandleStatus
.leftHandlePos
;
289 var rightHandlePos
= zoomHandleStatus
.rightHandlePos
;
290 var rangeSize
= rightHandlePos
- leftHandlePos
;
291 if (leftHandlePos
+ delX
<= self
.canvasRect_
.x
) {
292 leftHandlePos
= self
.canvasRect_
.x
;
293 rightHandlePos
= leftHandlePos
+ rangeSize
;
294 } else if (rightHandlePos
+ delX
>= self
.canvasRect_
.x
+ self
.canvasRect_
.w
) {
295 rightHandlePos
= self
.canvasRect_
.x
+ self
.canvasRect_
.w
;
296 leftHandlePos
= rightHandlePos
- rangeSize
;
298 leftHandlePos
+= delX
;
299 rightHandlePos
+= delX
;
301 var halfHandleWidth
= self
.leftZoomHandle_
.width
/2;
302 self
.leftZoomHandle_
.style
.left
= (leftHandlePos
- halfHandleWidth
) + 'px';
303 self
.rightZoomHandle_
.style
.left
= (rightHandlePos
- halfHandleWidth
) + 'px';
304 self
.drawInteractiveLayer_();
306 // Do pan on the fly (if not using excanvas).
307 if (!self
.isUsingExcanvas_
) {
312 onPanEnd
= function(e
) {
317 Dygraph
.removeEvent(topElem
, 'mousemove', onPan
);
318 Dygraph
.removeEvent(topElem
, 'mouseup', onPanEnd
);
319 // If using excanvas, do pan now.
320 if (self
.isUsingExcanvas_
) {
327 self
.isChangingRange_
= true;
328 self
.dygraph_
.dateWindow_
= toXDataWindow(self
.getZoomHandleStatus_());
329 self
.dygraph_
.drawGraph_(false);
331 self
.isChangingRange_
= false;
335 onCanvasMouseMove
= function(e
) {
336 if (isZooming
|| isPanning
) {
339 var cursor
= isMouseInPanZone(e
) ? 'move' : 'default';
340 if (cursor
!= self
.fgcanvas_
.style
.cursor
) {
341 self
.fgcanvas_
.style
.cursor
= cursor
;
345 var interactionModel
= {
346 mousedown
: function(event
, g
, context
) {
347 context
.initializeMouseDown(event
, g
, context
);
348 Dygraph
.startPan(event
, g
, context
);
350 mousemove
: function(event
, g
, context
) {
351 if (context
.isPanning
) {
352 Dygraph
.movePan(event
, g
, context
);
355 mouseup
: function(event
, g
, context
) {
356 if (context
.isPanning
) {
357 Dygraph
.endPan(event
, g
, context
);
362 this.dygraph_
.attrs_
.interactionModel
= interactionModel
;
363 this.dygraph_
.attrs_
.panEdgeFraction
= 0.0001;
365 var dragStartEvent
= window
.opera
? 'mousedown' : 'dragstart';
366 Dygraph
.addEvent(this.leftZoomHandle_
, dragStartEvent
, onZoomStart
);
367 Dygraph
.addEvent(this.rightZoomHandle_
, dragStartEvent
, onZoomStart
);
369 if (this.isUsingExcanvas_
) {
370 Dygraph
.addEvent(this.iePanOverlay_
, 'mousedown', onPanStart
);
372 Dygraph
.addEvent(this.fgcanvas_
, 'mousedown', onPanStart
);
373 Dygraph
.addEvent(this.fgcanvas_
, 'mousemove', onCanvasMouseMove
);
379 * Draws the static layer in the background canvas.
381 DygraphRangeSelector
.prototype.drawStaticLayer_
= function() {
382 var ctx
= this.bgcanvas_ctx_
;
383 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
385 this.drawMiniPlot_();
391 this.bgcanvas_ctx_
.lineWidth
= 1;
392 ctx
.strokeStyle
= 'gray';
394 ctx
.moveTo(margin
, margin
);
395 ctx
.lineTo(margin
, this.canvasRect_
.h
-margin
);
396 ctx
.lineTo(this.canvasRect_
.w
-margin
, this.canvasRect_
.h
-margin
);
397 ctx
.lineTo(this.canvasRect_
.w
-margin
, margin
);
404 * Draws the mini plot in the background canvas.
406 DygraphRangeSelector
.prototype.drawMiniPlot_
= function() {
407 var fillStyle
= this.attr_('rangeSelectorPlotFillColor');
408 var strokeStyle
= this.attr_('rangeSelectorPlotStrokeColor');
409 if (!fillStyle
&& !strokeStyle
) {
413 var combinedSeriesData
= this.computeCombinedSeriesAndLimits_();
414 var yRange
= combinedSeriesData
.yMax
- combinedSeriesData
.yMin
;
416 // Draw the mini plot.
417 var ctx
= this.bgcanvas_ctx_
;
420 var xExtremes
= this.dygraph_
.xAxisExtremes();
421 var xRange
= Math
.max(xExtremes
[1] - xExtremes
[0], 1.e
-30);
422 var xFact
= (this.canvasRect_
.w
- margin
)/xRange
;
423 var yFact
= (this.canvasRect_
.h
- margin
)/yRange
;
424 var canvasWidth
= this.canvasRect_
.w
- margin
;
425 var canvasHeight
= this.canvasRect_
.h
- margin
;
428 ctx
.moveTo(margin
, canvasHeight
);
429 for (var i
= 0; i
< combinedSeriesData
.data
.length
; i
++) {
430 var dataPoint
= combinedSeriesData
.data
[i
];
431 var x
= (dataPoint
[0] - xExtremes
[0])*xFact
;
432 var y
= canvasHeight
- (dataPoint
[1] - combinedSeriesData
.yMin
)*yFact
;
433 if (isFinite(x
) && isFinite(y
)) {
437 ctx
.lineTo(canvasWidth
, canvasHeight
);
441 var lingrad
= this.bgcanvas_ctx_
.createLinearGradient(0, 0, 0, canvasHeight
);
442 lingrad
.addColorStop(0, 'white');
443 lingrad
.addColorStop(1, fillStyle
);
444 this.bgcanvas_ctx_
.fillStyle
= lingrad
;
449 this.bgcanvas_ctx_
.strokeStyle
= strokeStyle
;
450 this.bgcanvas_ctx_
.lineWidth
= 1.5;
457 * Computes and returns the combinded series data along with min/max for the mini plot.
458 * @return {Object} An object containing combinded series array, ymin, ymax.
460 DygraphRangeSelector
.prototype.computeCombinedSeriesAndLimits_
= function() {
461 var data
= this.dygraph_
.rawData_
;
462 var logscale
= this.attr_('logscale');
464 // Create a combined series (average of all series values).
465 var combinedSeries
= [];
472 // Find out if data has multiple values per datapoint.
473 // Go to first data point that actually has values (see http://code.google.com/p/dygraphs/issues/detail
?id
=246)
474 for (i
= 0; i
< data
.length
; i
++) {
475 if (data
[i
].length
> 1 && data
[i
][1] != null) {
476 mutipleValues
= typeof data
[i
][1] != 'number';
480 for (k
= 0; k
< data
[0][1].length
; k
++) {
489 for (i
= 0; i
< data
.length
; i
++) {
490 var dataPoint
= data
[i
];
491 var xVal
= dataPoint
[0];
494 for (k
= 0; k
< sum
.length
; k
++) {
495 sum
[k
] = count
[k
] = 0;
501 for (j
= 1; j
< dataPoint
.length
; j
++) {
502 if (this.dygraph_
.visibility()[j
-1]) {
504 for (k
= 0; k
< sum
.length
; k
++) {
506 if (y
=== null || isNaN(y
)) continue;
512 if (y
=== null || isNaN(y
)) continue;
520 for (k
= 0; k
< sum
.length
; k
++) {
528 combinedSeries
.push([xVal
, yVal
]);
531 // Account for roll period, fractions.
532 combinedSeries
= this.dygraph_
.rollingAverage(combinedSeries
, this.dygraph_
.rollPeriod_
);
534 if (typeof combinedSeries
[0][1] != 'number') {
535 for (i
= 0; i
< combinedSeries
.length
; i
++) {
536 yVal
= combinedSeries
[i
][1];
537 combinedSeries
[i
][1] = yVal
[0];
541 // Compute the y range.
542 var yMin
= Number
.MAX_VALUE
;
543 var yMax
= -Number
.MAX_VALUE
;
544 for (i
= 0; i
< combinedSeries
.length
; i
++) {
545 yVal
= combinedSeries
[i
][1];
546 if (yVal
!== null && isFinite(yVal
) && (!logscale
|| yVal
> 0)) {
547 yMin
= Math
.min(yMin
, yVal
);
548 yMax
= Math
.max(yMax
, yVal
);
552 // Convert Y data to log scale if needed.
553 // Also, expand the Y range to compress the mini plot a little.
554 var extraPercent
= 0.25;
556 yMax
= Dygraph
.log10(yMax
);
557 yMax
+= yMax
*extraPercent
;
558 yMin
= Dygraph
.log10(yMin
);
559 for (i
= 0; i
< combinedSeries
.length
; i
++) {
560 combinedSeries
[i
][1] = Dygraph
.log10(combinedSeries
[i
][1]);
564 var yRange
= yMax
- yMin
;
565 if (yRange
<= Number
.MIN_VALUE
) {
566 yExtra
= yMax
*extraPercent
;
568 yExtra
= yRange
*extraPercent
;
574 return {data
: combinedSeries
, yMin
: yMin
, yMax
: yMax
};
579 * Places the zoom handles in the proper position based on the current X data window.
581 DygraphRangeSelector
.prototype.placeZoomHandles_
= function() {
582 var xExtremes
= this.dygraph_
.xAxisExtremes();
583 var xWindowLimits
= this.dygraph_
.xAxisRange();
584 var xRange
= xExtremes
[1] - xExtremes
[0];
585 var leftPercent
= Math
.max(0, (xWindowLimits
[0] - xExtremes
[0])/xRange
);
586 var rightPercent
= Math
.max(0, (xExtremes
[1] - xWindowLimits
[1])/xRange
);
587 var leftCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*leftPercent
;
588 var rightCoord
= this.canvasRect_
.x
+ this.canvasRect_
.w
*(1 - rightPercent
);
589 var handleTop
= Math
.max(this.canvasRect_
.y
, this.canvasRect_
.y
+ (this.canvasRect_
.h
- this.leftZoomHandle_
.height
)/2);
590 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
591 this.leftZoomHandle_
.style
.left
= (leftCoord
- halfHandleWidth
) + 'px';
592 this.leftZoomHandle_
.style
.top
= handleTop
+ 'px';
593 this.rightZoomHandle_
.style
.left
= (rightCoord
- halfHandleWidth
) + 'px';
594 this.rightZoomHandle_
.style
.top
= this.leftZoomHandle_
.style
.top
;
596 this.leftZoomHandle_
.style
.visibility
= 'visible';
597 this.rightZoomHandle_
.style
.visibility
= 'visible';
602 * Draws the interactive layer in the foreground canvas.
604 DygraphRangeSelector
.prototype.drawInteractiveLayer_
= function() {
605 var ctx
= this.fgcanvas_ctx_
;
606 ctx
.clearRect(0, 0, this.canvasRect_
.w
, this.canvasRect_
.h
);
608 var width
= this.canvasRect_
.w
- margin
;
609 var height
= this.canvasRect_
.h
- margin
;
610 var zoomHandleStatus
= this.getZoomHandleStatus_();
612 ctx
.strokeStyle
= 'black';
613 if (!zoomHandleStatus
.isZoomed
) {
615 ctx
.moveTo(margin
, margin
);
616 ctx
.lineTo(margin
, height
);
617 ctx
.lineTo(width
, height
);
618 ctx
.lineTo(width
, margin
);
620 if (this.iePanOverlay_
) {
621 this.iePanOverlay_
.style
.display
= 'none';
624 var leftHandleCanvasPos
= Math
.max(margin
, zoomHandleStatus
.leftHandlePos
- this.canvasRect_
.x
);
625 var rightHandleCanvasPos
= Math
.min(width
, zoomHandleStatus
.rightHandlePos
- this.canvasRect_
.x
);
627 ctx
.fillStyle
= 'rgba(240, 240, 240, 0.6)';
628 ctx
.fillRect(0, 0, leftHandleCanvasPos
, this.canvasRect_
.h
);
629 ctx
.fillRect(rightHandleCanvasPos
, 0, this.canvasRect_
.w
- rightHandleCanvasPos
, this.canvasRect_
.h
);
632 ctx
.moveTo(margin
, margin
);
633 ctx
.lineTo(leftHandleCanvasPos
, margin
);
634 ctx
.lineTo(leftHandleCanvasPos
, height
);
635 ctx
.lineTo(rightHandleCanvasPos
, height
);
636 ctx
.lineTo(rightHandleCanvasPos
, margin
);
637 ctx
.lineTo(width
, margin
);
640 if (this.isUsingExcanvas_
) {
641 this.iePanOverlay_
.style
.width
= (rightHandleCanvasPos
- leftHandleCanvasPos
) + 'px';
642 this.iePanOverlay_
.style
.left
= leftHandleCanvasPos
+ 'px';
643 this.iePanOverlay_
.style
.height
= height
+ 'px';
644 this.iePanOverlay_
.style
.display
= 'inline';
651 * Returns the current zoom handle position information.
652 * @return {Object} The zoom handle status.
654 DygraphRangeSelector
.prototype.getZoomHandleStatus_
= function() {
655 var halfHandleWidth
= this.leftZoomHandle_
.width
/2;
656 var leftHandlePos
= parseInt(this.leftZoomHandle_
.style
.left
, 10) + halfHandleWidth
;
657 var rightHandlePos
= parseInt(this.rightZoomHandle_
.style
.left
, 10) + halfHandleWidth
;
659 leftHandlePos
: leftHandlePos
,
660 rightHandlePos
: rightHandlePos
,
661 isZoomed
: (leftHandlePos
- 1 > this.canvasRect_
.x
|| rightHandlePos
+ 1 < this.canvasRect_
.x
+this.canvasRect_
.w
)