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