1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 | "use strict";
|
15 |
|
16 | import * as utils from '../dygraph-utils';
|
17 | import DygraphInteraction from '../dygraph-interaction-model';
|
18 | import IFrameTarp from '../iframe-tarp';
|
19 |
|
20 | var rangeSelector = function() {
|
21 | this.hasTouchInterface_ = typeof(TouchEvent) != 'undefined';
|
22 | this.isMobileDevice_ = /mobile|android/gi.test(navigator.appVersion);
|
23 | this.interfaceCreated_ = false;
|
24 | };
|
25 |
|
26 | rangeSelector.prototype.toString = function() {
|
27 | return "RangeSelector Plugin";
|
28 | };
|
29 |
|
30 | rangeSelector.prototype.activate = function(dygraph) {
|
31 | this.dygraph_ = dygraph;
|
32 | if (this.getOption_('showRangeSelector')) {
|
33 | this.createInterface_();
|
34 | }
|
35 | return {
|
36 | layout: this.reserveSpace_,
|
37 | predraw: this.renderStaticLayer_,
|
38 | didDrawChart: this.renderInteractiveLayer_
|
39 | };
|
40 | };
|
41 |
|
42 | rangeSelector.prototype.destroy = function() {
|
43 | this.bgcanvas_ = null;
|
44 | this.fgcanvas_ = null;
|
45 | this.leftZoomHandle_ = null;
|
46 | this.rightZoomHandle_ = null;
|
47 | };
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 | rangeSelector.prototype.getOption_ = function(name, opt_series) {
|
54 | return this.dygraph_.getOption(name, opt_series);
|
55 | };
|
56 |
|
57 | rangeSelector.prototype.setDefaultOption_ = function(name, value) {
|
58 | this.dygraph_.attrs_[name] = value;
|
59 | };
|
60 |
|
61 |
|
62 |
|
63 |
|
64 |
|
65 | rangeSelector.prototype.createInterface_ = function() {
|
66 | this.createCanvases_();
|
67 | this.createZoomHandles_();
|
68 | this.initInteraction_();
|
69 |
|
70 |
|
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);
|
74 | }
|
75 |
|
76 | this.interfaceCreated_ = true;
|
77 | this.addToGraph_();
|
78 | };
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
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_);
|
90 | };
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
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;
|
103 | };
|
104 |
|
105 |
|
106 |
|
107 |
|
108 |
|
109 | rangeSelector.prototype.reserveSpace_ = function(e) {
|
110 | if (this.getOption_('showRangeSelector')) {
|
111 | e.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4);
|
112 | }
|
113 | };
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 | rangeSelector.prototype.renderStaticLayer_ = function() {
|
120 | if (!this.updateVisibility_()) {
|
121 | return;
|
122 | }
|
123 | this.resize_();
|
124 | this.drawStaticLayer_();
|
125 | };
|
126 |
|
127 |
|
128 |
|
129 |
|
130 |
|
131 | rangeSelector.prototype.renderInteractiveLayer_ = function() {
|
132 | if (!this.updateVisibility_() || this.isChangingRange_) {
|
133 | return;
|
134 | }
|
135 | this.placeZoomHandles_();
|
136 | this.drawInteractiveLayer_();
|
137 | };
|
138 |
|
139 |
|
140 |
|
141 |
|
142 |
|
143 | rangeSelector.prototype.updateVisibility_ = function() {
|
144 | var enabled = this.getOption_('showRangeSelector');
|
145 | if (enabled) {
|
146 | if (!this.interfaceCreated_) {
|
147 | this.createInterface_();
|
148 | } else if (!this.graphDiv_ || !this.graphDiv_.parentNode) {
|
149 | this.addToGraph_();
|
150 | }
|
151 | } else if (this.graphDiv_) {
|
152 | this.removeFromGraph_();
|
153 | var dygraph = this.dygraph_;
|
154 | setTimeout(function() { dygraph.width_ = 0; dygraph.resize(); }, 1);
|
155 | }
|
156 | return enabled;
|
157 | };
|
158 |
|
159 |
|
160 |
|
161 |
|
162 |
|
163 | rangeSelector.prototype.resize_ = function() {
|
164 | function setElementRect(canvas, context, rect, pixelRatioOption) {
|
165 | var canvasScale = pixelRatioOption || utils.getContextPixelRatio(context);
|
166 |
|
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';
|
173 |
|
174 | if(canvasScale != 1) {
|
175 | context.scale(canvasScale, canvasScale);
|
176 | }
|
177 | }
|
178 |
|
179 | var plotArea = this.dygraph_.layout_.getPlotArea();
|
180 |
|
181 | var xAxisLabelHeight = 0;
|
182 | if (this.dygraph_.getOptionForAxis('drawAxis', 'x')) {
|
183 | xAxisLabelHeight = this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'));
|
184 | }
|
185 | this.canvasRect_ = {
|
186 | x: plotArea.x,
|
187 | y: plotArea.y + plotArea.h + xAxisLabelHeight + 4,
|
188 | w: plotArea.w,
|
189 | h: this.getOption_('rangeSelectorHeight')
|
190 | };
|
191 |
|
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);
|
195 | };
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
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_);
|
207 |
|
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_);
|
214 | };
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
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';
|
226 | img.style.cursor = 'col-resize';
|
227 |
|
228 | img.width = 9;
|
229 | img.height = 16;
|
230 | img.src = 'data:image/png;base64,' +
|
231 | 'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' +
|
232 | 'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' +
|
233 | 'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' +
|
234 | '6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' +
|
235 | 'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII=';
|
236 |
|
237 | if (this.isMobileDevice_) {
|
238 | img.width *= 2;
|
239 | img.height *= 2;
|
240 | }
|
241 |
|
242 | this.leftZoomHandle_ = img;
|
243 | this.rightZoomHandle_ = img.cloneNode(false);
|
244 | };
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 | rangeSelector.prototype.initInteraction_ = function() {
|
251 | var self = this;
|
252 | var topElem = document;
|
253 | var clientXLast = 0;
|
254 | var handle = null;
|
255 | var isZooming = false;
|
256 | var isPanning = false;
|
257 | var dynamic = !this.isMobileDevice_;
|
258 |
|
259 |
|
260 |
|
261 | var tarp = new IFrameTarp();
|
262 |
|
263 |
|
264 |
|
265 | var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone,
|
266 | onPanStart, onPan, onPanEnd, doPan, onCanvasHover;
|
267 |
|
268 |
|
269 | var onZoomHandleTouchEvent, onCanvasTouchEvent, addTouchEvents;
|
270 |
|
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];
|
277 | };
|
278 |
|
279 | onZoomStart = function(e) {
|
280 | utils.cancelEvent(e);
|
281 | isZooming = true;
|
282 | clientXLast = e.clientX;
|
283 | handle = e.target ? e.target : e.srcElement;
|
284 | if (e.type === 'mousedown' || e.type === 'dragstart') {
|
285 |
|
286 | utils.addEvent(topElem, 'mousemove', onZoom);
|
287 | utils.addEvent(topElem, 'mouseup', onZoomEnd);
|
288 | }
|
289 | self.fgcanvas_.style.cursor = 'col-resize';
|
290 | tarp.cover();
|
291 | return true;
|
292 | };
|
293 |
|
294 | onZoom = function(e) {
|
295 | if (!isZooming) {
|
296 | return false;
|
297 | }
|
298 | utils.cancelEvent(e);
|
299 |
|
300 | var delX = e.clientX - clientXLast;
|
301 | if (Math.abs(delX) < 4) {
|
302 | return true;
|
303 | }
|
304 | clientXLast = e.clientX;
|
305 |
|
306 |
|
307 | var zoomHandleStatus = self.getZoomHandleStatus_();
|
308 | var newPos;
|
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);
|
313 | } else {
|
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);
|
317 | }
|
318 | var halfHandleWidth = handle.width/2;
|
319 | handle.style.left = (newPos - halfHandleWidth) + 'px';
|
320 | self.drawInteractiveLayer_();
|
321 |
|
322 |
|
323 | if (dynamic) {
|
324 | doZoom();
|
325 | }
|
326 | return true;
|
327 | };
|
328 |
|
329 | onZoomEnd = function(e) {
|
330 | if (!isZooming) {
|
331 | return false;
|
332 | }
|
333 | isZooming = false;
|
334 | tarp.uncover();
|
335 | utils.removeEvent(topElem, 'mousemove', onZoom);
|
336 | utils.removeEvent(topElem, 'mouseup', onZoomEnd);
|
337 | self.fgcanvas_.style.cursor = 'default';
|
338 |
|
339 |
|
340 | if (!dynamic) {
|
341 | doZoom();
|
342 | }
|
343 | return true;
|
344 | };
|
345 |
|
346 | doZoom = function() {
|
347 | try {
|
348 | var zoomHandleStatus = self.getZoomHandleStatus_();
|
349 | self.isChangingRange_ = true;
|
350 | if (!zoomHandleStatus.isZoomed) {
|
351 | self.dygraph_.resetZoom();
|
352 | } else {
|
353 | var xDataWindow = toXDataWindow(zoomHandleStatus);
|
354 | self.dygraph_.doZoomXDates_(xDataWindow[0], xDataWindow[1]);
|
355 | }
|
356 | } finally {
|
357 | self.isChangingRange_ = false;
|
358 | }
|
359 | };
|
360 |
|
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);
|
367 | };
|
368 |
|
369 | onPanStart = function(e) {
|
370 | if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) {
|
371 | utils.cancelEvent(e);
|
372 | isPanning = true;
|
373 | clientXLast = e.clientX;
|
374 | if (e.type === 'mousedown') {
|
375 |
|
376 | utils.addEvent(topElem, 'mousemove', onPan);
|
377 | utils.addEvent(topElem, 'mouseup', onPanEnd);
|
378 | }
|
379 | return true;
|
380 | }
|
381 | return false;
|
382 | };
|
383 |
|
384 | onPan = function(e) {
|
385 | if (!isPanning) {
|
386 | return false;
|
387 | }
|
388 | utils.cancelEvent(e);
|
389 |
|
390 | var delX = e.clientX - clientXLast;
|
391 | if (Math.abs(delX) < 4) {
|
392 | return true;
|
393 | }
|
394 | clientXLast = e.clientX;
|
395 |
|
396 |
|
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;
|
407 | } else {
|
408 | leftHandlePos += delX;
|
409 | rightHandlePos += delX;
|
410 | }
|
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_();
|
415 |
|
416 |
|
417 | if (dynamic) {
|
418 | doPan();
|
419 | }
|
420 | return true;
|
421 | };
|
422 |
|
423 | onPanEnd = function(e) {
|
424 | if (!isPanning) {
|
425 | return false;
|
426 | }
|
427 | isPanning = false;
|
428 | utils.removeEvent(topElem, 'mousemove', onPan);
|
429 | utils.removeEvent(topElem, 'mouseup', onPanEnd);
|
430 |
|
431 | if (!dynamic) {
|
432 | doPan();
|
433 | }
|
434 | return true;
|
435 | };
|
436 |
|
437 | doPan = function() {
|
438 | try {
|
439 | self.isChangingRange_ = true;
|
440 | self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_());
|
441 | self.dygraph_.drawGraph_(false);
|
442 | } finally {
|
443 | self.isChangingRange_ = false;
|
444 | }
|
445 | };
|
446 |
|
447 | onCanvasHover = function(e) {
|
448 | if (isZooming || isPanning) {
|
449 | return;
|
450 | }
|
451 | var cursor = isMouseInPanZone(e) ? 'move' : 'default';
|
452 | if (cursor != self.fgcanvas_.style.cursor) {
|
453 | self.fgcanvas_.style.cursor = cursor;
|
454 | }
|
455 | };
|
456 |
|
457 | onZoomHandleTouchEvent = function(e) {
|
458 | if (e.type == 'touchstart' && e.targetTouches.length == 1) {
|
459 | if (onZoomStart(e.targetTouches[0])) {
|
460 | utils.cancelEvent(e);
|
461 | }
|
462 | } else if (e.type == 'touchmove' && e.targetTouches.length == 1) {
|
463 | if (onZoom(e.targetTouches[0])) {
|
464 | utils.cancelEvent(e);
|
465 | }
|
466 | } else {
|
467 | onZoomEnd(e);
|
468 | }
|
469 | };
|
470 |
|
471 | onCanvasTouchEvent = function(e) {
|
472 | if (e.type == 'touchstart' && e.targetTouches.length == 1) {
|
473 | if (onPanStart(e.targetTouches[0])) {
|
474 | utils.cancelEvent(e);
|
475 | }
|
476 | } else if (e.type == 'touchmove' && e.targetTouches.length == 1) {
|
477 | if (onPan(e.targetTouches[0])) {
|
478 | utils.cancelEvent(e);
|
479 | }
|
480 | } else {
|
481 | onPanEnd(e);
|
482 | }
|
483 | };
|
484 |
|
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);
|
489 | }
|
490 | };
|
491 |
|
492 | this.setDefaultOption_('interactionModel', DygraphInteraction.dragIsPanInteractionModel);
|
493 | this.setDefaultOption_('panEdgeFraction', 0.0001);
|
494 |
|
495 | var dragStartEvent = window.opera ? 'mousedown' : 'dragstart';
|
496 | this.dygraph_.addAndTrackEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart);
|
497 | this.dygraph_.addAndTrackEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart);
|
498 |
|
499 | this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousedown', onPanStart);
|
500 | this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousemove', onCanvasHover);
|
501 |
|
502 |
|
503 | if (this.hasTouchInterface_) {
|
504 | addTouchEvents(this.leftZoomHandle_, onZoomHandleTouchEvent);
|
505 | addTouchEvents(this.rightZoomHandle_, onZoomHandleTouchEvent);
|
506 | addTouchEvents(this.fgcanvas_, onCanvasTouchEvent);
|
507 | }
|
508 | };
|
509 |
|
510 |
|
511 |
|
512 |
|
513 |
|
514 | rangeSelector.prototype.drawStaticLayer_ = function() {
|
515 | var ctx = this.bgcanvas_ctx_;
|
516 | ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h);
|
517 | try {
|
518 | this.drawMiniPlot_();
|
519 | } catch(ex) {
|
520 | console.warn(ex);
|
521 | }
|
522 |
|
523 | var margin = 0.5;
|
524 | this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorBackgroundLineWidth');
|
525 | ctx.strokeStyle = this.getOption_('rangeSelectorBackgroundStrokeColor');
|
526 | ctx.beginPath();
|
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);
|
531 | ctx.stroke();
|
532 | };
|
533 |
|
534 |
|
535 |
|
536 |
|
537 |
|
538 | rangeSelector.prototype.drawMiniPlot_ = function() {
|
539 | var fillStyle = this.getOption_('rangeSelectorPlotFillColor');
|
540 | var fillGradientStyle = this.getOption_('rangeSelectorPlotFillGradientColor');
|
541 | var strokeStyle = this.getOption_('rangeSelectorPlotStrokeColor');
|
542 | if (!fillStyle && !strokeStyle) {
|
543 | return;
|
544 | }
|
545 |
|
546 | var stepPlot = this.getOption_('stepPlot');
|
547 |
|
548 | var combinedSeriesData = this.computeCombinedSeriesAndLimits_();
|
549 | var yRange = combinedSeriesData.yMax - combinedSeriesData.yMin;
|
550 |
|
551 |
|
552 | var ctx = this.bgcanvas_ctx_;
|
553 | var margin = 0.5;
|
554 |
|
555 | var xExtremes = this.dygraph_.xAxisExtremes();
|
556 | var xRange = Math.max(xExtremes[1] - xExtremes[0], 1.e-30);
|
557 | var xFact = (this.canvasRect_.w - margin)/xRange;
|
558 | var yFact = (this.canvasRect_.h - margin)/yRange;
|
559 | var canvasWidth = this.canvasRect_.w - margin;
|
560 | var canvasHeight = this.canvasRect_.h - margin;
|
561 |
|
562 | var prevX = null, prevY = null;
|
563 |
|
564 | ctx.beginPath();
|
565 | ctx.moveTo(margin, canvasHeight);
|
566 | for (var i = 0; i < combinedSeriesData.data.length; i++) {
|
567 | var dataPoint = combinedSeriesData.data[i];
|
568 | var x = ((dataPoint[0] !== null) ? ((dataPoint[0] - xExtremes[0])*xFact) : NaN);
|
569 | var y = ((dataPoint[1] !== null) ? (canvasHeight - (dataPoint[1] - combinedSeriesData.yMin)*yFact) : NaN);
|
570 |
|
571 |
|
572 |
|
573 | if (!stepPlot && prevX !== null && Math.round(x) == Math.round(prevX)) {
|
574 | continue;
|
575 | }
|
576 |
|
577 | if (isFinite(x) && isFinite(y)) {
|
578 | if(prevX === null) {
|
579 | ctx.lineTo(x, canvasHeight);
|
580 | }
|
581 | else if (stepPlot) {
|
582 | ctx.lineTo(x, prevY);
|
583 | }
|
584 | ctx.lineTo(x, y);
|
585 | prevX = x;
|
586 | prevY = y;
|
587 | }
|
588 | else {
|
589 | if(prevX !== null) {
|
590 | if (stepPlot) {
|
591 | ctx.lineTo(x, prevY);
|
592 | ctx.lineTo(x, canvasHeight);
|
593 | }
|
594 | else {
|
595 | ctx.lineTo(prevX, canvasHeight);
|
596 | }
|
597 | }
|
598 | prevX = prevY = null;
|
599 | }
|
600 | }
|
601 | ctx.lineTo(canvasWidth, canvasHeight);
|
602 | ctx.closePath();
|
603 |
|
604 | if (fillStyle) {
|
605 | var lingrad = this.bgcanvas_ctx_.createLinearGradient(0, 0, 0, canvasHeight);
|
606 | if (fillGradientStyle) {
|
607 | lingrad.addColorStop(0, fillGradientStyle);
|
608 | }
|
609 | lingrad.addColorStop(1, fillStyle);
|
610 | this.bgcanvas_ctx_.fillStyle = lingrad;
|
611 | ctx.fill();
|
612 | }
|
613 |
|
614 | if (strokeStyle) {
|
615 | this.bgcanvas_ctx_.strokeStyle = strokeStyle;
|
616 | this.bgcanvas_ctx_.lineWidth = this.getOption_('rangeSelectorPlotLineWidth');
|
617 | ctx.stroke();
|
618 | }
|
619 | };
|
620 |
|
621 |
|
622 |
|
623 |
|
624 |
|
625 |
|
626 |
|
627 |
|
628 | rangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() {
|
629 | var g = this.dygraph_;
|
630 | var logscale = this.getOption_('logscale');
|
631 | var i;
|
632 |
|
633 |
|
634 | var numColumns = g.numColumns();
|
635 | var labels = g.getLabels();
|
636 | var includeSeries = new Array(numColumns);
|
637 | var anySet = false;
|
638 | var visibility = g.visibility();
|
639 | var inclusion = [];
|
640 |
|
641 | for (i = 1; i < numColumns; i++) {
|
642 | var include = this.getOption_('showInRangeSelector', labels[i]);
|
643 | inclusion.push(include);
|
644 | if (include !== null) anySet = true;
|
645 | }
|
646 |
|
647 | if (anySet) {
|
648 | for (i = 1; i < numColumns; i++) {
|
649 | includeSeries[i] = inclusion[i - 1];
|
650 | }
|
651 | } else {
|
652 | for (i = 1; i < numColumns; i++) {
|
653 | includeSeries[i] = visibility[i - 1];
|
654 | }
|
655 | }
|
656 |
|
657 |
|
658 |
|
659 | var rolledSeries = [];
|
660 | var dataHandler = g.dataHandler_;
|
661 | var options = g.attributes_;
|
662 | for (i = 1; i < g.numColumns(); i++) {
|
663 | if (!includeSeries[i]) continue;
|
664 | var series = dataHandler.extractSeries(g.rawData_, i, options);
|
665 | if (g.rollPeriod() > 1) {
|
666 | series = dataHandler.rollingAverage(series, g.rollPeriod(), options, i);
|
667 | }
|
668 |
|
669 | rolledSeries.push(series);
|
670 | }
|
671 |
|
672 | var combinedSeries = [];
|
673 | for (i = 0; i < rolledSeries[0].length; i++) {
|
674 | var sum = 0;
|
675 | var count = 0;
|
676 | for (var j = 0; j < rolledSeries.length; j++) {
|
677 | var y = rolledSeries[j][i][1];
|
678 | if (y === null || isNaN(y)) continue;
|
679 | count++;
|
680 | sum += y;
|
681 | }
|
682 | combinedSeries.push([rolledSeries[0][i][0], sum / count]);
|
683 | }
|
684 |
|
685 |
|
686 | var yMin = Number.MAX_VALUE;
|
687 | var yMax = -Number.MAX_VALUE;
|
688 | for (i = 0; i < combinedSeries.length; i++) {
|
689 | var yVal = combinedSeries[i][1];
|
690 | if (yVal !== null && isFinite(yVal) && (!logscale || yVal > 0)) {
|
691 | yMin = Math.min(yMin, yVal);
|
692 | yMax = Math.max(yMax, yVal);
|
693 | }
|
694 | }
|
695 |
|
696 |
|
697 |
|
698 | var extraPercent = 0.25;
|
699 | if (logscale) {
|
700 | yMax = utils.log10(yMax);
|
701 | yMax += yMax*extraPercent;
|
702 | yMin = utils.log10(yMin);
|
703 | for (i = 0; i < combinedSeries.length; i++) {
|
704 | combinedSeries[i][1] = utils.log10(combinedSeries[i][1]);
|
705 | }
|
706 | } else {
|
707 | var yExtra;
|
708 | var yRange = yMax - yMin;
|
709 | if (yRange <= Number.MIN_VALUE) {
|
710 | yExtra = yMax*extraPercent;
|
711 | } else {
|
712 | yExtra = yRange*extraPercent;
|
713 | }
|
714 | yMax += yExtra;
|
715 | yMin -= yExtra;
|
716 | }
|
717 |
|
718 | return {data: combinedSeries, yMin: yMin, yMax: yMax};
|
719 | };
|
720 |
|
721 |
|
722 |
|
723 |
|
724 |
|
725 | rangeSelector.prototype.placeZoomHandles_ = function() {
|
726 | var xExtremes = this.dygraph_.xAxisExtremes();
|
727 | var xWindowLimits = this.dygraph_.xAxisRange();
|
728 | var xRange = xExtremes[1] - xExtremes[0];
|
729 | var leftPercent = Math.max(0, (xWindowLimits[0] - xExtremes[0])/xRange);
|
730 | var rightPercent = Math.max(0, (xExtremes[1] - xWindowLimits[1])/xRange);
|
731 | var leftCoord = this.canvasRect_.x + this.canvasRect_.w*leftPercent;
|
732 | var rightCoord = this.canvasRect_.x + this.canvasRect_.w*(1 - rightPercent);
|
733 | var handleTop = Math.max(this.canvasRect_.y, this.canvasRect_.y + (this.canvasRect_.h - this.leftZoomHandle_.height)/2);
|
734 | var halfHandleWidth = this.leftZoomHandle_.width/2;
|
735 | this.leftZoomHandle_.style.left = (leftCoord - halfHandleWidth) + 'px';
|
736 | this.leftZoomHandle_.style.top = handleTop + 'px';
|
737 | this.rightZoomHandle_.style.left = (rightCoord - halfHandleWidth) + 'px';
|
738 | this.rightZoomHandle_.style.top = this.leftZoomHandle_.style.top;
|
739 |
|
740 | this.leftZoomHandle_.style.visibility = 'visible';
|
741 | this.rightZoomHandle_.style.visibility = 'visible';
|
742 | };
|
743 |
|
744 |
|
745 |
|
746 |
|
747 |
|
748 | rangeSelector.prototype.drawInteractiveLayer_ = function() {
|
749 | var ctx = this.fgcanvas_ctx_;
|
750 | ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h);
|
751 | var margin = 1;
|
752 | var width = this.canvasRect_.w - margin;
|
753 | var height = this.canvasRect_.h - margin;
|
754 | var zoomHandleStatus = this.getZoomHandleStatus_();
|
755 |
|
756 | ctx.strokeStyle = this.getOption_('rangeSelectorForegroundStrokeColor');
|
757 | ctx.lineWidth = this.getOption_('rangeSelectorForegroundLineWidth');
|
758 | if (!zoomHandleStatus.isZoomed) {
|
759 | ctx.beginPath();
|
760 | ctx.moveTo(margin, margin);
|
761 | ctx.lineTo(margin, height);
|
762 | ctx.lineTo(width, height);
|
763 | ctx.lineTo(width, margin);
|
764 | ctx.stroke();
|
765 | } else {
|
766 | var leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x);
|
767 | var rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x);
|
768 |
|
769 | const veilColour = this.getOption_('rangeSelectorVeilColour');
|
770 | ctx.fillStyle = veilColour ? veilColour :
|
771 | ('rgba(240, 240, 240, ' + this.getOption_('rangeSelectorAlpha').toString() + ')');
|
772 | ctx.fillRect(0, 0, leftHandleCanvasPos, this.canvasRect_.h);
|
773 | ctx.fillRect(rightHandleCanvasPos, 0, this.canvasRect_.w - rightHandleCanvasPos, this.canvasRect_.h);
|
774 |
|
775 | ctx.beginPath();
|
776 | ctx.moveTo(margin, margin);
|
777 | ctx.lineTo(leftHandleCanvasPos, margin);
|
778 | ctx.lineTo(leftHandleCanvasPos, height);
|
779 | ctx.lineTo(rightHandleCanvasPos, height);
|
780 | ctx.lineTo(rightHandleCanvasPos, margin);
|
781 | ctx.lineTo(width, margin);
|
782 | ctx.stroke();
|
783 | }
|
784 | };
|
785 |
|
786 |
|
787 |
|
788 |
|
789 |
|
790 |
|
791 | rangeSelector.prototype.getZoomHandleStatus_ = function() {
|
792 | var halfHandleWidth = this.leftZoomHandle_.width/2;
|
793 | var leftHandlePos = parseFloat(this.leftZoomHandle_.style.left) + halfHandleWidth;
|
794 | var rightHandlePos = parseFloat(this.rightZoomHandle_.style.left) + halfHandleWidth;
|
795 | return {
|
796 | leftHandlePos: leftHandlePos,
|
797 | rightHandlePos: rightHandlePos,
|
798 | isZoomed: (leftHandlePos - 1 > this.canvasRect_.x || rightHandlePos + 1 < this.canvasRect_.x+this.canvasRect_.w)
|
799 | };
|
800 | };
|
801 |
|
802 | export default rangeSelector;
|