Fix issue 186 (dygraphs does not accept "1e-5" as a numeric x-value).
[dygraphs.git] / dygraph-range-selector.js
CommitLineData
ccd9d7c2
PF
1// Copyright 2011 Paul Felix (paul.eric.felix@gmail.com)
2// All Rights Reserved.
3
4/**
5 * @fileoverview This file contains the DygraphRangeSelector class used to provide
6 * a timeline range selector widget for dygraphs.
7 */
8
9/**
10 * The DygraphRangeSelector class provides a timeline range selector widget.
11 * @param {Dygraph} dygraph The dygraph object
12 * @constructor
13 */
14DygraphRangeSelector = function(dygraph) {
15 this.isIE_ = /MSIE/.test(navigator.userAgent) && !window.opera;
920208fb 16 this.isUsingExcanvas_ = dygraph.isUsingExcanvas_;
ccd9d7c2
PF
17 this.dygraph_ = dygraph;
18 this.createCanvases_();
920208fb
PF
19 if (this.isUsingExcanvas_) {
20 this.createIEPanOverlay_();
21 }
ccd9d7c2
PF
22 this.createZoomHandles_();
23 this.initInteraction_();
24};
25
26/**
27 * Adds the range selector to the dygraph.
28 * @param {Object} graphDiv The container div for the range selector.
29 * @param {DygraphLayout} layout The DygraphLayout object for this graph.
30 */
31DygraphRangeSelector.prototype.addToGraph = function(graphDiv, layout) {
32 this.layout_ = layout;
33 this.resize_();
34 graphDiv.appendChild(this.bgcanvas_);
35 graphDiv.appendChild(this.fgcanvas_);
36 graphDiv.appendChild(this.leftZoomHandle_);
37 graphDiv.appendChild(this.rightZoomHandle_);
38};
39
40/**
41 * Renders the static background portion of the range selector.
42 */
43DygraphRangeSelector.prototype.renderStaticLayer = function() {
44 this.resize_();
45 this.drawStaticLayer_();
46};
47
48/**
49 * Renders the interactive foreground portion of the range selector.
50 */
51DygraphRangeSelector.prototype.renderInteractiveLayer = function() {
52 if (this.isChangingRange_) {
53 return;
54 }
ccd9d7c2
PF
55 this.placeZoomHandles_();
56 this.drawInteractiveLayer_();
57};
58
59/**
60 * @private
61 * Resizes the range selector.
62 */
63DygraphRangeSelector.prototype.resize_ = function() {
920208fb 64 function setElementRect(canvas, rect) {
ccd9d7c2
PF
65 canvas.style.top = rect.y + 'px';
66 canvas.style.left = rect.x + 'px';
67 canvas.width = rect.w;
68 canvas.height = rect.h;
69 canvas.style.width = canvas.width + 'px'; // for IE
70 canvas.style.height = canvas.height + 'px'; // for IE
71 };
72
70be5ed1 73 var plotArea = this.layout_.getPlotArea();
ccd9d7c2
PF
74 var xAxisLabelHeight = this.attr_('axisLabelFontSize') + 2 * this.attr_('axisTickSize');
75 this.canvasRect_ = {
76 x: plotArea.x,
77 y: plotArea.y + plotArea.h + xAxisLabelHeight + 4,
78 w: plotArea.w,
79 h: this.attr_('rangeSelectorHeight')
80 };
81
920208fb
PF
82 setElementRect(this.bgcanvas_, this.canvasRect_);
83 setElementRect(this.fgcanvas_, this.canvasRect_);
ccd9d7c2
PF
84};
85
86DygraphRangeSelector.prototype.attr_ = function(name) {
87 return this.dygraph_.attr_(name);
88};
89
90/**
91 * @private
92 * Creates the background and foreground canvases.
93 */
94DygraphRangeSelector.prototype.createCanvases_ = function() {
95 this.bgcanvas_ = Dygraph.createCanvas();
88c4a47e 96 this.bgcanvas_.className = 'dygraph-rangesel-bgcanvas';
ccd9d7c2 97 this.bgcanvas_.style.position = 'absolute';
920208fb 98 this.bgcanvas_.style.zIndex = 9;
ccd9d7c2
PF
99 this.bgcanvas_ctx_ = Dygraph.getContext(this.bgcanvas_);
100
101 this.fgcanvas_ = Dygraph.createCanvas();
88c4a47e 102 this.fgcanvas_.className = 'dygraph-rangesel-fgcanvas';
ccd9d7c2 103 this.fgcanvas_.style.position = 'absolute';
920208fb 104 this.fgcanvas_.style.zIndex = 9;
ccd9d7c2
PF
105 this.fgcanvas_.style.cursor = 'default';
106 this.fgcanvas_ctx_ = Dygraph.getContext(this.fgcanvas_);
107};
108
109/**
110 * @private
920208fb
PF
111 * Creates overlay divs for IE/Excanvas so that mouse events are handled properly.
112 */
113DygraphRangeSelector.prototype.createIEPanOverlay_ = function() {
114 this.iePanOverlay_ = document.createElement("div");
115 this.iePanOverlay_.style.position = 'absolute';
116 this.iePanOverlay_.style.backgroundColor = 'white';
117 this.iePanOverlay_.style.filter = 'alpha(opacity=0)';
118 this.iePanOverlay_.style.display = 'none';
119 this.iePanOverlay_.style.cursor = 'move';
120 this.fgcanvas_.appendChild(this.iePanOverlay_);
121};
122
123/**
124 * @private
ccd9d7c2
PF
125 * Creates the zoom handle elements.
126 */
127DygraphRangeSelector.prototype.createZoomHandles_ = function() {
128 var img = new Image();
88c4a47e 129 img.className = 'dygraph-rangesel-zoomhandle';
ccd9d7c2 130 img.style.position = 'absolute';
920208fb 131 img.style.zIndex = 10;
ccd9d7c2
PF
132 img.style.visibility = 'hidden'; // Initially hidden so they don't show up in the wrong place.
133 img.style.cursor = 'col-resize';
920208fb
PF
134 if (/MSIE 7/.test(navigator.userAgent)) { // IE7 doesn't support embedded src data.
135 img.width = 7;
136 img.height = 14;
137 img.style.backgroundColor = 'white';
138 img.style.border = '1px solid #333333'; // Just show box in IE7.
139 } else {
140 img.width = 9;
141 img.height = 16;
142 img.src = 'data:image/png;base64,\
ccd9d7c2
PF
143iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA\
144zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv\
145bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl\
1466YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s\
147qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=';
920208fb 148 }
ccd9d7c2
PF
149
150 this.leftZoomHandle_ = img;
151 this.rightZoomHandle_ = img.cloneNode(false);
152};
153
154/**
155 * @private
156 * Sets up the interaction for the range selector.
157 */
158DygraphRangeSelector.prototype.initInteraction_ = function() {
159 var self = this;
160 var topElem = this.isIE_ ? document : window;
161 var xLast = 0;
162 var handle = null;
163 var isZooming = false;
164 var isPanning = false;
165
166 function toXDataWindow(zoomHandleStatus) {
167 var xDataLimits = self.dygraph_.xAxisExtremes();
168 var fact = (xDataLimits[1] - xDataLimits[0])/self.canvasRect_.w;
169 var xDataMin = xDataLimits[0] + (zoomHandleStatus.leftHandlePos - self.canvasRect_.x)*fact;
170 var xDataMax = xDataLimits[0] + (zoomHandleStatus.rightHandlePos - self.canvasRect_.x)*fact;
171 return [xDataMin, xDataMax];
172 };
173
174 function onZoomStart(e) {
175 Dygraph.cancelEvent(e);
176 isZooming = true;
177 xLast = e.screenX;
178 handle = e.target ? e.target : e.srcElement;
179 Dygraph.addEvent(topElem, 'mousemove', onZoom);
180 Dygraph.addEvent(topElem, 'mouseup', onZoomEnd);
181 self.fgcanvas_.style.cursor = 'col-resize';
182 };
183
184 function onZoom(e) {
185 if (!isZooming) {
186 return;
187 }
188 var delX = e.screenX - xLast;
189 if (Math.abs(delX) < 4) {
190 return;
191 }
192 xLast = e.screenX;
193 var zoomHandleStatus = self.getZoomHandleStatus_();
ccd9d7c2
PF
194 if (handle == self.leftZoomHandle_) {
195 var newPos = zoomHandleStatus.leftHandlePos + delX;
196 newPos = Math.min(newPos, zoomHandleStatus.rightHandlePos - handle.width - 3);
197 newPos = Math.max(newPos, self.canvasRect_.x);
198 } else {
199 var newPos = zoomHandleStatus.rightHandlePos + delX;
200 newPos = Math.min(newPos, self.canvasRect_.x + self.canvasRect_.w);
201 newPos = Math.max(newPos, zoomHandleStatus.leftHandlePos + handle.width + 3);
202 }
920208fb 203 var halfHandleWidth = handle.width/2;
ccd9d7c2
PF
204 handle.style.left = (newPos - halfHandleWidth) + 'px';
205 self.drawInteractiveLayer_();
206
207 // Zoom on the fly (if not using excanvas).
920208fb 208 if (!self.isUsingExcanvas_) {
ccd9d7c2
PF
209 doZoom();
210 }
211 };
212
213 function onZoomEnd(e) {
214 if (!isZooming) {
215 return;
216 }
217 isZooming = false;
218 Dygraph.removeEvent(topElem, 'mousemove', onZoom);
219 Dygraph.removeEvent(topElem, 'mouseup', onZoomEnd);
220 self.fgcanvas_.style.cursor = 'default';
221
222 // If using excanvas, Zoom now.
920208fb 223 if (self.isUsingExcanvas_) {
ccd9d7c2
PF
224 doZoom();
225 }
226 };
227
228 function doZoom() {
229 try {
230 var zoomHandleStatus = self.getZoomHandleStatus_();
231 self.isChangingRange_ = true;
232 if (!zoomHandleStatus.isZoomed) {
233 self.dygraph_.doUnzoom_();
234 } else {
235 var xDataWindow = toXDataWindow(zoomHandleStatus);
236 self.dygraph_.doZoomXDates_(xDataWindow[0], xDataWindow[1]);
237 }
238 } finally {
239 self.isChangingRange_ = false;
240 }
241 };
242
243 function isMouseInPanZone(e) {
920208fb
PF
244 if (self.isUsingExcanvas_) {
245 return e.srcElement == self.iePanOverlay_;
246 } else {
247 // Getting clientX directly from the event is not accurate enough :(
248 var clientX = self.canvasRect_.x + (e.layerX != undefined ? e.layerX : e.offsetX);
249 var zoomHandleStatus = self.getZoomHandleStatus_();
250 return (clientX > zoomHandleStatus.leftHandlePos && clientX < zoomHandleStatus.rightHandlePos);
251 }
ccd9d7c2
PF
252 };
253
254 function onPanStart(e) {
255 if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) {
256 Dygraph.cancelEvent(e);
257 isPanning = true;
258 xLast = e.screenX;
259 Dygraph.addEvent(topElem, 'mousemove', onPan);
260 Dygraph.addEvent(topElem, 'mouseup', onPanEnd);
261 }
262 };
263
264 function onPan(e) {
265 if (!isPanning) {
266 return;
267 }
920208fb 268 Dygraph.cancelEvent(e);
ccd9d7c2
PF
269
270 var delX = e.screenX - xLast;
271 if (Math.abs(delX) < 4) {
272 return;
273 }
274 xLast = e.screenX;
275
276 // Move range view
277 var zoomHandleStatus = self.getZoomHandleStatus_();
278 var leftHandlePos = zoomHandleStatus.leftHandlePos;
279 var rightHandlePos = zoomHandleStatus.rightHandlePos;
280 var rangeSize = rightHandlePos - leftHandlePos;
281 if (leftHandlePos + delX <= self.canvasRect_.x) {
282 leftHandlePos = self.canvasRect_.x;
283 rightHandlePos = leftHandlePos + rangeSize;
284 } else if (rightHandlePos + delX >= self.canvasRect_.x + self.canvasRect_.w) {
285 rightHandlePos = self.canvasRect_.x + self.canvasRect_.w;
286 leftHandlePos = rightHandlePos - rangeSize;
287 } else {
288 leftHandlePos += delX;
289 rightHandlePos += delX;
290 }
920208fb 291 var halfHandleWidth = self.leftZoomHandle_.width/2;
ccd9d7c2
PF
292 self.leftZoomHandle_.style.left = (leftHandlePos - halfHandleWidth) + 'px';
293 self.rightZoomHandle_.style.left = (rightHandlePos - halfHandleWidth) + 'px';
294 self.drawInteractiveLayer_();
295
296 // Do pan on the fly (if not using excanvas).
920208fb 297 if (!self.isUsingExcanvas_) {
ccd9d7c2
PF
298 doPan();
299 }
300 };
301
302 function onPanEnd(e) {
303 if (!isPanning) {
304 return;
305 }
306 isPanning = false;
307 Dygraph.removeEvent(topElem, 'mousemove', onPan);
308 Dygraph.removeEvent(topElem, 'mouseup', onPanEnd);
309 // If using excanvas, do pan now.
920208fb 310 if (self.isUsingExcanvas_) {
ccd9d7c2
PF
311 doPan();
312 }
313 };
314
315 function doPan() {
316 try {
317 self.isChangingRange_ = true;
318 self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_());
319 self.dygraph_.drawGraph_(false);
320 } finally {
321 self.isChangingRange_ = false;
322 }
323 };
324
325 function onCanvasMouseMove(e) {
326 if (isZooming || isPanning) {
327 return;
328 }
329 var cursor = isMouseInPanZone(e) ? 'move' : 'default';
330 if (cursor != self.fgcanvas_.style.cursor) {
331 self.fgcanvas_.style.cursor = cursor;
332 }
333 };
334
335 var interactionModel = {
336 mousedown: function(event, g, context) {
337 context.initializeMouseDown(event, g, context);
338 Dygraph.startPan(event, g, context);
339 },
340 mousemove: function(event, g, context) {
341 if (context.isPanning) {
342 Dygraph.movePan(event, g, context);
343 }
344 },
345 mouseup: function(event, g, context) {
346 if (context.isPanning) {
347 Dygraph.endPan(event, g, context);
348 }
349 }
350 };
351
352 this.dygraph_.attrs_.interactionModel = interactionModel;
353 this.dygraph_.attrs_.panEdgeFraction = .0001;
354
920208fb
PF
355 var dragStartEvent = window.opera ? 'mousedown' : 'dragstart';
356 Dygraph.addEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart);
357 Dygraph.addEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart);
358
359 if (this.isUsingExcanvas_) {
360 Dygraph.addEvent(this.iePanOverlay_, 'mousedown', onPanStart);
361 } else {
362 Dygraph.addEvent(this.fgcanvas_, 'mousedown', onPanStart);
363 Dygraph.addEvent(this.fgcanvas_, 'mousemove', onCanvasMouseMove);
364 }
ccd9d7c2
PF
365};
366
367/**
368 * @private
369 * Draws the static layer in the background canvas.
370 */
371DygraphRangeSelector.prototype.drawStaticLayer_ = function() {
372 var ctx = this.bgcanvas_ctx_;
373 ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h);
ccd9d7c2
PF
374 try {
375 this.drawMiniPlot_();
376 } catch(ex) {
377 }
920208fb
PF
378
379 var margin = .5;
380 this.bgcanvas_ctx_.lineWidth = 1;
381 ctx.strokeStyle = 'gray';
382 ctx.beginPath();
383 ctx.moveTo(margin, margin);
384 ctx.lineTo(margin, this.canvasRect_.h-margin);
385 ctx.lineTo(this.canvasRect_.w-margin, this.canvasRect_.h-margin);
386 ctx.lineTo(this.canvasRect_.w-margin, margin);
387 ctx.stroke();
ccd9d7c2
PF
388};
389
390
391/**
392 * @private
393 * Draws the mini plot in the background canvas.
394 */
395DygraphRangeSelector.prototype.drawMiniPlot_ = function() {
396 var fillStyle = this.attr_('rangeSelectorPlotFillColor');
397 var strokeStyle = this.attr_('rangeSelectorPlotStrokeColor');
398 if (!fillStyle && !strokeStyle) {
399 return;
400 }
401
402 var combinedSeriesData = this.computeCombinedSeriesAndLimits_();
403 var yRange = combinedSeriesData.yMax - combinedSeriesData.yMin;
404
405 // Draw the mini plot.
406 var ctx = this.bgcanvas_ctx_;
407 var margin = .5;
408
409 var xExtremes = this.dygraph_.xAxisExtremes();
410 var xRange = Math.max(xExtremes[1] - xExtremes[0], 1.e-30);
411 var xFact = (this.canvasRect_.w - margin)/xRange;
412 var yFact = (this.canvasRect_.h - margin)/yRange;
413 var canvasWidth = this.canvasRect_.w - margin;
414 var canvasHeight = this.canvasRect_.h - margin;
415
416 ctx.beginPath();
417 ctx.moveTo(margin, canvasHeight);
418 for (var i = 0; i < combinedSeriesData.data.length; i++) {
419 var dataPoint = combinedSeriesData.data[i];
420 var x = (dataPoint[0] - xExtremes[0])*xFact;
421 var y = canvasHeight - (dataPoint[1] - combinedSeriesData.yMin)*yFact;
422 if (isFinite(x) && isFinite(y)) {
423 ctx.lineTo(x, y);
424 }
425 }
426 ctx.lineTo(canvasWidth, canvasHeight);
427 ctx.closePath();
428
429 if (fillStyle) {
430 var lingrad = this.bgcanvas_ctx_.createLinearGradient(0, 0, 0, canvasHeight);
431 lingrad.addColorStop(0, 'white');
432 lingrad.addColorStop(1, fillStyle);
433 this.bgcanvas_ctx_.fillStyle = lingrad;
434 ctx.fill();
435 }
436
437 if (strokeStyle) {
438 this.bgcanvas_ctx_.strokeStyle = strokeStyle;
439 this.bgcanvas_ctx_.lineWidth = 1.5;
440 ctx.stroke();
441 }
442};
443
444/**
445 * @private
446 * Computes and returns the combinded series data along with min/max for the mini plot.
447 * @return {Object} An object containing combinded series array, ymin, ymax.
448 */
449DygraphRangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() {
450 var data = this.dygraph_.rawData_;
451 var logscale = this.attr_('logscale');
452
453 // Create a combined series (average of all series values).
454 var combinedSeries = [];
455 var sum;
456 var count;
457 var mutipleValues = typeof data[0][1] != 'number';
458
459 if (mutipleValues) {
460 sum = [];
461 count = [];
462 for (var k = 0; k < data[0][1].length; k++) {
463 sum.push(0);
464 count.push(0);
465 }
466 mutipleValues = true;
467 }
468
469 for (var i = 0; i < data.length; i++) {
470 var dataPoint = data[i];
471 var xVal = dataPoint[0];
472 var yVal;
473
474 if (mutipleValues) {
475 for (var k = 0; k < sum.length; k++) {
476 sum[k] = count[k] = 0;
477 }
478 } else {
479 sum = count = 0;
480 }
481
482 for (var j = 1; j < dataPoint.length; j++) {
483 if (this.dygraph_.visibility()[j-1]) {
484 if (mutipleValues) {
485 for (var k = 0; k < sum.length; k++) {
486 var y = dataPoint[j][k];
487 if (y == null || isNaN(y)) continue;
488 sum[k] += y;
489 count[k]++;
490 }
491 } else {
492 var y = dataPoint[j];
493 if (y == null || isNaN(y)) continue;
494 sum += y;
495 count++;
496 }
497 }
498 }
499
500 if (mutipleValues) {
501 for (var k = 0; k < sum.length; k++) {
502 sum[k] /= count[k];
503 }
504 yVal = sum.slice(0);
505 } else {
506 yVal = sum/count;
507 }
508
509 combinedSeries.push([xVal, yVal]);
510 }
511
512 // Account for roll period, fractions.
513 combinedSeries = this.dygraph_.rollingAverage(combinedSeries, this.dygraph_.rollPeriod_);
514
515 if (typeof combinedSeries[0][1] != 'number') {
516 for (var i = 0; i < combinedSeries.length; i++) {
517 var yVal = combinedSeries[i][1];
518 combinedSeries[i][1] = yVal[0];
519 }
520 }
521
522 // Compute the y range.
523 var yMin = Number.MAX_VALUE;
524 var yMax = -Number.MAX_VALUE;
525 for (var i = 0; i < combinedSeries.length; i++) {
526 var yVal = combinedSeries[i][1];
920208fb 527 if (yVal != null && isFinite(yVal) && (!logscale || yVal > 0)) {
ccd9d7c2
PF
528 yMin = Math.min(yMin, yVal);
529 yMax = Math.max(yMax, yVal);
530 }
531 }
532
533 // Convert Y data to log scale if needed.
534 // Also, expand the Y range to compress the mini plot a little.
535 var extraPercent = .25;
536 if (logscale) {
537 yMax = Dygraph.log10(yMax);
538 yMax += yMax*extraPercent;
539 yMin = Dygraph.log10(yMin);
540 for (var i = 0; i < combinedSeries.length; i++) {
541 combinedSeries[i][1] = Dygraph.log10(combinedSeries[i][1]);
542 }
543 } else {
544 var yExtra;
545 yRange = yMax - yMin;
546 if (yRange <= Number.MIN_VALUE) {
547 yExtra = yMax*extraPercent;
548 } else {
549 yExtra = yRange*extraPercent;
550 }
551 yMax += yExtra;
552 yMin -= yExtra;
553 }
554
555 return {data: combinedSeries, yMin: yMin, yMax: yMax};
556};
557
558/**
559 * @private
560 * Places the zoom handles in the proper position based on the current X data window.
561 */
562DygraphRangeSelector.prototype.placeZoomHandles_ = function() {
563 var xExtremes = this.dygraph_.xAxisExtremes();
564 var xWindowLimits = this.dygraph_.xAxisRange();
565 var xRange = xExtremes[1] - xExtremes[0];
566 var leftPercent = Math.max(0, (xWindowLimits[0] - xExtremes[0])/xRange);
567 var rightPercent = Math.max(0, (xExtremes[1] - xWindowLimits[1])/xRange);
568 var leftCoord = this.canvasRect_.x + this.canvasRect_.w*leftPercent;
569 var rightCoord = this.canvasRect_.x + this.canvasRect_.w*(1 - rightPercent);
920208fb
PF
570 var handleTop = Math.max(this.canvasRect_.y, this.canvasRect_.y + (this.canvasRect_.h - this.leftZoomHandle_.height)/2);
571 var halfHandleWidth = this.leftZoomHandle_.width/2;
572 this.leftZoomHandle_.style.left = (leftCoord - halfHandleWidth) + 'px';
ccd9d7c2 573 this.leftZoomHandle_.style.top = handleTop + 'px';
920208fb 574 this.rightZoomHandle_.style.left = (rightCoord - halfHandleWidth) + 'px';
ccd9d7c2
PF
575 this.rightZoomHandle_.style.top = this.leftZoomHandle_.style.top;
576
577 this.leftZoomHandle_.style.visibility = 'visible';
578 this.rightZoomHandle_.style.visibility = 'visible';
579};
580
581/**
582 * @private
583 * Draws the interactive layer in the foreground canvas.
584 */
585DygraphRangeSelector.prototype.drawInteractiveLayer_ = function() {
586 var ctx = this.fgcanvas_ctx_;
587 ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h);
588 var margin = 1;
589 var width = this.canvasRect_.w - margin;
590 var height = this.canvasRect_.h - margin;
591 var zoomHandleStatus = this.getZoomHandleStatus_();
592
593 ctx.strokeStyle = 'black';
594 if (!zoomHandleStatus.isZoomed) {
595 ctx.beginPath();
596 ctx.moveTo(margin, margin);
597 ctx.lineTo(margin, height);
598 ctx.lineTo(width, height);
599 ctx.lineTo(width, margin);
600 ctx.stroke();
920208fb
PF
601 if (this.iePanOverlay_) {
602 this.iePanOverlay_.style.display = 'none';
603 }
ccd9d7c2
PF
604 } else {
605 leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x);
606 rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x);
607
608 ctx.fillStyle = 'rgba(240, 240, 240, 0.6)';
920208fb
PF
609 ctx.fillRect(0, 0, leftHandleCanvasPos, this.canvasRect_.h);
610 ctx.fillRect(rightHandleCanvasPos, 0, this.canvasRect_.w - rightHandleCanvasPos, this.canvasRect_.h);
ccd9d7c2
PF
611
612 ctx.beginPath();
613 ctx.moveTo(margin, margin);
614 ctx.lineTo(leftHandleCanvasPos, margin);
615 ctx.lineTo(leftHandleCanvasPos, height);
616 ctx.lineTo(rightHandleCanvasPos, height);
617 ctx.lineTo(rightHandleCanvasPos, margin);
618 ctx.lineTo(width, margin);
619 ctx.stroke();
920208fb
PF
620
621 if (this.isUsingExcanvas_) {
622 this.iePanOverlay_.style.width = (rightHandleCanvasPos - leftHandleCanvasPos) + 'px';
623 this.iePanOverlay_.style.left = leftHandleCanvasPos + 'px';
624 this.iePanOverlay_.style.height = height + 'px';
625 this.iePanOverlay_.style.display = 'inline';
626 }
ccd9d7c2
PF
627 }
628};
629
630/**
631 * @private
632 * Returns the current zoom handle position information.
633 * @return {Object} The zoom handle status.
634 */
635DygraphRangeSelector.prototype.getZoomHandleStatus_ = function() {
920208fb 636 var halfHandleWidth = this.leftZoomHandle_.width/2;
ccd9d7c2
PF
637 var leftHandlePos = parseInt(this.leftZoomHandle_.style.left) + halfHandleWidth;
638 var rightHandlePos = parseInt(this.rightZoomHandle_.style.left) + halfHandleWidth;
639 return {
640 leftHandlePos: leftHandlePos,
641 rightHandlePos: rightHandlePos,
642 isZoomed: (leftHandlePos - 1 > this.canvasRect_.x || rightHandlePos + 1 < this.canvasRect_.x+this.canvasRect_.w)
643 };
644};