PageSourceSearch

https://moderndive.com/libs/dygraphs-binding-1.1.1.6/dygraphs.js

js moderndive.com collected 2026-09-24 10:04:10 UTC 27,860 bytes, 789 lines download raw bytes

1
2// polyfill indexOf for IE8
3if (!Array.prototype.indexOf) {
4  Array.prototype.indexOf = function(elt /*, from*/) {
5    var len = this.length >>> 0;
6
7    var from = Number(arguments[1]) || 0;
8    from = (from < 0)
9         ? Math.ceil(from)
10         : Math.floor(from);
11    if (from < 0)
12      from += len;
13
14    for (; from < len; from++) {
15      if (from in this &&
16          this[from] === elt)
17        return from;
18    }
19    return -1;
20  };
21}
22
23HTMLWidgets.widget({
24
25  name: "dygraphs",
26
27  type: "output",
28
29  factory: function(el, width, height) {
30    
31    // reference to dygraph
32    var dygraph = null;
33    
34    // reference to widget global groups
35    var groups = this.groups;
36 
37    // add qt style if we are running under Qt
38    if (window.navigator.userAgent.indexOf(" Qt/") > 0)
39      el.className += " qt";
40    
41    return {
42      
43      renderValue: function(x) {
44        
45        // reference to this for closures
46        var thiz = this;
47        
48        // get dygraph attrs and populate file field
49        var attrs = x.attrs;
50        attrs.file = x.data;
51	      
52	// disable zoom interaction except for clicks
53        if (attrs.disableZoom) {
54          attrs.interactionModel = Dygraph.Interaction.nonInteractiveModel_;
55        }
56        
57        // convert non-arrays to arrays
58        for (var index = 0; index < attrs.file.length; index++) {
59          if (!$.isArray(attrs.file[index]))
60            attrs.file[index] = [].concat(attrs.file[index]);
61        }
62            
63        // resolve "auto" legend behavior
64        if (x.attrs.legend == "auto") {
65          if (x.data.length <= 2)
66            x.attrs.legend = "onmouseover";
67          else
68            x.attrs.legend = "always";
69        }
70        
71        if (x.format == "date") {
72          
73          // set appropriated function in case of fixed tz
74          if ((attrs.axes.x.axisLabelFormatter === undefined) && x.fixedtz)
75            attrs.axes.x.axisLabelFormatter = this.xAxisLabelFormatterFixedTZ(x.tzone);
76            
77          if ((attrs.axes.x.valueFormatter === undefined) && x.fixedtz)
78            attrs.axes.x.valueFormatter = this.xValueFormatterFixedTZ(x.scale, x.tzone);
79      
80          if ((attrs.axes.x.ticker === undefined) && x.fixedtz)
81            attrs.axes.x.ticker = this.customDateTickerFixedTZ(x.tzone);
82        
83          // provide an automatic x value formatter if none is already specified
84          if ((attrs.axes.x.valueFormatter === undefined) && (x.fixedtz != true))
85            attrs.axes.x.valueFormatter = this.xValueFormatter(x.scale);
86          
87          // convert time to js time
88          attrs.file[0] = attrs.file[0].map(function(value) {
89            return thiz.normalizeDateValue(x.scale, value, x.fixedtz);
90          });
91          if (attrs.dateWindow != null) {
92            attrs.dateWindow = attrs.dateWindow.map(function(value) {
93              var date = thiz.normalizeDateValue(x.scale, value, x.fixedtz);
94              return date.getTime();
95            });
96          }
97        }
98        
99        
100        // transpose array
101        attrs.file = HTMLWidgets.transposeArray2D(attrs.file);
102        
103        // add drawCallback for group
104        if (x.group != null)
105          this.addGroupDrawCallback(x);  
106          
107        // add shading and event callback if necessary
108        this.addShadingCallback(x);
109        this.addEventCallback(x);
110        this.addZoomCallback(x);
111        
112        // disable y-axis touch events on mobile phones
113        if (attrs.mobileDisableYTouch !== false && this.isMobilePhone()) {
114          // create default interaction model if necessary
115          if (!attrs.interactionModel)
116            attrs.interactionModel = Dygraph.Interaction.defaultModel;
117          // disable y touch direction
118          attrs.interactionModel.touchstart = function(event, dygraph, context) {
119            Dygraph.defaultInteractionModel.touchstart(event, dygraph, context);
120            context.touchDirections = { x: true, y: false };
121          };
122        }
123    
124        // create plugins
125        if (x.plugins) {
126          attrs.plugins = [];
127          for (var plugin in x.plugins) {
128            if (x.plugins.hasOwnProperty(plugin)) {
129              
130              // get plugin options
131              var options = x.plugins[plugin];
132              
133              // create plugin and add to dygraph
134              var p = new Dygraph.Plugins[plugin](options);
135              attrs.plugins.push(p);
136            }
137          }
138        }
139
140        // custom plotter
141        if (x.plotter) {
142          attrs.plotter = Dygraph.Plotters[x.plotter];
143        }
144
145        // custom data handler
146        if (x.dataHandler) {
147          attrs.dataHandler = Dygraph.DataHandlers[x.dataHandler];
148        }
149
150        // custom circles
151        if (x.pointShape) {
152          if (typeof x.pointShape === 'string') {
153            attrs.drawPointCallback = Dygraph.Circles[x.pointShape.toUpperCase()];
154            attrs.drawHighlightPointCallback = Dygraph.Circles[x.pointShape.toUpperCase()];
155          } else {
156            for (var s in x.pointShape) {
157              if (x.pointShape.hasOwnProperty(s)) {
158                attrs.series[s].drawPointCallback = Dygraph.Circles[x.pointShape[s].toUpperCase()];
159                attrs.series[s].drawHighlightPointCallback = Dygraph.Circles[x.pointShape[s].toUpperCase()];
160              }
161            }
162          }
163        }
164    
165        // if there is no existing dygraph perform initialization
166        if (!dygraph) {
167          
168          // subscribe to custom shown event (fired by ioslides to trigger
169          // shiny reactivity but we can use it as well). this is necessary
170          // because if a dygraph starts out as display:none it has height
171          // and width == 0 and this doesn't change when it becomes visible
172          $(el).closest('slide').on('shown', function() {
173            if (dygraph)
174              dygraph.resize();  
175          });
176          
177          // do the same for reveal.js
178          $(el).closest('section.slide').on('shown', function() {
179            if (dygraph)
180              dygraph.resize();  
181          });
182          
183          // redraw on R Markdown {.tabset} tab visibility changed
184          var tab = $(el).closest('div.tab-pane');
185          if (tab !== null) {
186            var tabID = tab.attr('id');
187            var tabAnchor = $('a[data-toggle="tab"][href="#' + tabID + '"]');
188            if (tabAnchor !== null) {
189              tabAnchor.on('shown.bs.tab', function() {
190                if (dygraph)
191                  dygraph.resize();  
192              });
193            }
194          }
195          // add default font for viewer mode
196          if (this.queryVar("viewer_pane") === "1")
197            document.body.style.fontFamily = "Arial, sans-serif";
198    
199          // inject css if necessary
200          if (x.css != null) {
201            var style = document.createElement('style');
202            style.type = 'text/css';
203            if (style.styleSheet) 
204              style.styleSheet.cssText = x.css;
205            else 
206              style.appendChild(document.createTextNode(x.css));
207            document.getElementsByTagName("head")[0].appendChild(style);
208          }
209          
210        } else {
211          
212            // retain the userDateWindow if requested
213            if (dygraph.userDateWindow != null
214                && attrs.retainDateWindow == true) {
215              attrs.dateWindow = dygraph.xAxisRange();
216            }
217                
218            // remove it from groups if it's there
219            if (x.group != null && groups[x.group] != null) {
220              var index = groups[x.group].indexOf(dygraph);
221              if (index != -1)
222                groups[x.group].splice(index, 1);
223            }
224            
225            // destroy the existing dygraph 
226            dygraph.destroy();
227            dygraph = null;
228        }
229        
230        // create the dygraph and add it to it's group (if any)
231        dygraph = thiz.dygraph = new Dygraph(el, attrs.file, attrs);
232        dygraph.userDateWindow = attrs.dateWindow;
233        if (x.group != null)
234          groups[x.group].push(dygraph);
235   	
236        // add shiny inputs for date window and click
237        if (HTMLWidgets.shinyMode) {
238          var isDate = x.format == "date";
239          this.addClickShinyInput(el.id, isDate);
240          this.addDateWindowShinyInput(el.id, isDate);
241        }
242        
243        // set annotations
244        if (x.annotations != null) {
245          dygraph.ready(function() {
246            if (x.format == "date") {
247              x.annotations.map(function(annotation) {
248                var date = thiz.normalizeDateValue(x.scale, annotation.x, x.fixedtz);
249                annotation.x = date.getTime();
250              });
251            }
252            dygraph.setAnnotations(x.annotations);
253          }); 
254        }
255          
256      },
257      
258      customDateTickerFixedTZ : function(tz){
259        return function(t,e,a,i,r) {   
260          var a=Dygraph.pickDateTickGranularity(t,e,a,i);
261          if(a >= 0){
262            
263            var n=i("axisLabelFormatter"),
264            o=i("labelsUTC"),
265            s=o?Dygraph.DateAccessorsUTC:Dygraph.DateAccessorsLocal;
266            l=Dygraph.TICK_PLACEMENT[a].datefield;
267            h=Dygraph.TICK_PLACEMENT[a].step;
268            p=Dygraph.TICK_PLACEMENT[a].spacing;
269            
270            var y = [];
271            var d = moment(t);
272            d.tz(tz); 
273            d.millisecond(0);
274          
275            if(l > Dygraph.DATEFIELD_M){
276              var x;
277              if (l === Dygraph.DATEFIELD_SS) {  // seconds 
278                x = d.second();         
279                d.second(x - x % h);     
280              } else if(l === Dygraph.DATEFIELD_MM){
281                d.second(0)
282                x = d.minute();
283                d.minute(x - x % h);
284              } else if(l === Dygraph.DATEFIELD_HH){
285                d.second(0);
286                d.minute(0);
287                x = d.hour();
288                d.hour(x - x % h);
289              } else if(l === Dygraph.DATEFIELD_D){
290                d.second(0);
291                d.minute(0);
292                d.hour(0);
293                if (h == 7) {  // one week
294                    d.startOf('week');
295                }
296              }
297              
298              v = d.valueOf();
299              _=moment(v).tz(tz);
300            
301              // For spacings coarser than two-hourly, we want to ignore daylight
302              // savings transitions to get consistent ticks. For finer-grained ticks,
303              // it's essential to show the DST transition in all its messiness.
304              var start_offset_min = moment(v).tz(tz).zone();
305              var check_dst = (p >= Dygraph.TICK_PLACEMENT[Dygraph.TWO_HOURLY].spacing);
306              
307    	        if(a<=Dygraph.HOURLY){
308    		        for(t>v&&(v+=p,_=moment(v).tz(tz));e>=v;){
309    			        y.push({v:v,label:n(_,a,i,r)});
310    			        v+=p;
311    			        _=moment(v).tz(tz);
312    		        }
313    	        }else{
314                for(t>v&&(v+=p,_=moment(v).tz(tz));e>=v;){  
315                
316                  // This ensures that we stay on the same hourly "rhythm" across
317                  // daylight savings transitions. Without this, the ticks could get off
318                  // by an hour. See tests/daylight-savings.html or issue 147.
319                  if (check_dst && _.zone() != start_offset_min) {
320                    var delta_min = _.zone() - start_offset_min;
321                    v += delta_min * 60 * 1000;
322                    _= moment(v).tz(tz);
323                    start_offset_min = _.zone();
324    
325                    // Check whether we've backed into the previous timezone again.
326                    // This can happen during a "spring forward" transition. In this case,
327                    // it's best to skip this tick altogether (we may be shooting for a
328                    // non-existent time like the 2AM that's skipped) and go to the next
329                    // one.
330                    if (moment(v + p).tz(tz).zone() != start_offset_min) {
331                      v += p;
332                      _= moment(v).tz(tz);
333                      start_offset_min = _.zone();
334                    }
335                  }
336                
337                  (a>=Dygraph.DAILY||_.get('hour')%h===0)&&y.push({v:v,label:n(_,a,i,r)});
338    			        v+=p;
339    			        _=moment(v).tz(tz);
340    		        }
341    	        }
342    	      }else{
343              var start_year = moment(t).tz(tz).year();
344              var end_year   = moment(e).tz(tz).year();
345              var start_month = moment(t).tz(tz).month();
346              
347              if(l === Dygraph.DATEFIELD_M){
348                var step_month = h;
349                for (var ii = start_year; ii <= end_year; ii++) {
350                  for (var j = 0; j < 12;) {
351                    var dt = moment(new Date(ii, j, 1)).tz(tz); 
352                    // fix some tz bug
353                    dt.year(ii);
354                    dt.month(j);
355                    dt.date(1);
356                    dt.hour(0);
357                    v = dt.valueOf();
358                    y.push({v:v,label:n(moment(v).tz(tz),a,i,r)});
359                    j+=step_month;
360                  }
361                }
362              }else{
363                var step_year = h;
364                for (var ii = start_year; ii <= end_year;) {
365                  var dt = moment(new Date(ii, 1, 1)).tz(tz); 
366                  // fix some tz bug
367                  dt.year(ii);
368                  dt.month(j);
369                  dt.date(1);
370                  dt.hour(0);
371                  v = dt.valueOf();
372                  y.push({v:v,label:n(moment(v).tz(tz),a,i,r)});
373                  ii+=step_year;
374                }
375              }
376    	      }
377    	      return y;
378    	    }else{
379           return []; 
380    	    }
381        };
382      },
383    
384      xAxisLabelFormatterFixedTZ : function(tz){
385      
386        return function dateAxisFormatter(date, granularity){
387          var mmnt = moment(date).tz(tz);
388          if (granularity >= Dygraph.DECADAL){
389            return mmnt.format('YYYY');
390          }else{
391            if(granularity >= Dygraph.MONTHLY){
392              return mmnt.format('MMM YYYY');
393            }else{
394              var frac = mmnt.hour() * 3600 + mmnt.minute() * 60 + mmnt.second() + mmnt.millisecond();
395                if (frac === 0 || granularity >= Dygraph.DAILY) {
396                  return mmnt.format('DD MMM');
397                } else {
398                 if (mmnt.second()) {
399                   return mmnt.format('HH:mm:ss');
400                 } else {
401                   return mmnt.format('HH:mm');
402                 }
403                }
404             } 
405                            
406           }         
407       }
408      },
409             
410      xValueFormatterFixedTZ: function(scale, tz) {
411                       
412        return function(millis) {
413          var mmnt = moment(millis).tz(tz);
414          if (scale == "yearly")
415            return mmnt.format('YYYY') + ' (' + mmnt.zoneAbbr() + ')';
416          else if (scale == "quarterly")
417            return mmnt.fquarter(1) + ' (' + mmnt.zoneAbbr() + ')';
418            else if (scale == "monthly")
419              return mmnt.format('MMM, YYYY')+ ' (' + mmnt.zoneAbbr() + ')';
420            else if (scale == "daily" || scale == "weekly")
421              return mmnt.format('MMM, DD, YYYY')+ ' (' + mmnt.zoneAbbr() + ')';
422            else
423              return mmnt.format('dddd, MMMM DD, YYYY HH:mm:ss')+ ' (' + mmnt.zoneAbbr() + ')';
424        }
425      },
426      
427      xValueFormatter: function(scale) {
428        
429        var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", 
430                          "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
431                          
432        return function(millis) {
433          var date = new Date(millis);
434          if (scale == "yearly")
435            return date.getFullYear();
436          else if (scale == "quarterly")
437            return moment(millis).fquarter(1);
438          else if (scale == "monthly")
439            return monthNames[date.getMonth()] + ', ' + date.getFullYear(); 
440          else if (scale == "daily" || scale == "weekly")
441            return monthNames[date.getMonth()] + ', ' + 
442                              date.getDate() + ', ' + 
443                              date.getFullYear();
444          else
445            return date.toLocaleString();
446        }
447      },
448      
449      addZoomCallback: function(x) {
450        
451        // alias this
452        var thiz = this;
453        
454        // get attrs
455        var attrs = x.attrs;
456        
457        // check for an existing zoomCallback
458        var prevZoomCallback = attrs["zoomCallback"];
459        
460        attrs.zoomCallback = function(minDate, maxDate, yRanges) {
461          
462          // call existing
463          if (prevZoomCallback)
464            prevZoomCallback(minDate, maxDate, yRanges);
465            
466          // record user date window (or lack thereof)
467          if (dygraph.xAxisExtremes()[0] != minDate ||
468              dygraph.xAxisExtremes()[1] != maxDate) {
469             dygraph.userDateWindow = [minDate, maxDate];
470          } else {
471             dygraph.userDateWindow = null;
472          }
473          
474          // record in group if necessary
475          if (x.group != null && groups[x.group] != null) {
476            var group = groups[x.group];
477            for(var i = 0; i<group.length; i++)
478              group[i].userDateWindow = dygraph.userDateWindow;
479          }
480        };
481      },
482      
483      addGroupDrawCallback: function(x) {
484        
485        // get attrs
486        var attrs = x.attrs;
487        
488        // check for an existing drawCallback
489        var prevDrawCallback = attrs["drawCallback"];
490        
491        groups[x.group] = groups[x.group] || [];
492        var group = groups[x.group];
493        var blockRedraw = false;
494        attrs.drawCallback = function(me, initial) {
495          
496          // call existing
497          if (prevDrawCallback)
498            prevDrawCallback(me, initial);
499          
500          // sync peers in group
501          if (blockRedraw || initial) return;
502          blockRedraw = true;
503          var range = dygraph.xAxisRange();
504          for (var j = 0; j < group.length; j++) {
505            if (group[j] == me) continue;
506            // update group range only if it's different (prevents
507            // infinite recursion in updateOptions)
508            var peerRange = group[j].xAxisRange();
509            if (peerRange[0] != range[0] || peerRange[1] != range[1]) {
510              group[j].updateOptions({
511                dateWindow: range
512              });
513            }
514          }
515          blockRedraw = false;
516        };
517      },
518      
519      addShadingCallback: function(x) {
520        
521        // bail if no shadings
522        if (x.shadings.length == 0)
523          return;
524        
525        // alias this
526        var thiz = this;
527        
528        // get attrs
529        var attrs = x.attrs;
530        
531        // check for an existing underlayCallback
532        var prevUnderlayCallback = attrs["underlayCallback"];
533        
534        // install callback
535        attrs.underlayCallback = function(canvas, area, g) {
536          
537          // call existing
538          if (prevUnderlayCallback)
539            prevUnderlayCallback(canvas, area, g);
540            
541          for (var i = 0; i < x.shadings.length; i++) {
542            var shading = x.shadings[i];
543            canvas.save();
544            canvas.fillStyle = shading.color;
545            if (shading.axis == "x") {
546              var x1 = shading.from;
547              var x2 = shading.to;
548              if (x.format == "date") {
549                x1 = thiz.normalizeDateValue(x.scale, x1, x.fixedtz).getTime();
550                x2 = thiz.normalizeDateValue(x.scale, x2, x.fixedtz).getTime();
551              }
552              var left = g.toDomXCoord(x1);
553              var right = g.toDomXCoord(x2);
554              
555              canvas.fillRect(left, area.y, right - left, area.h);
556            } else if (shading.axis == "y") {
557              var bottom = g.toDomYCoord(shading.from);
558              var top = g.toDomYCoord(shading.to);
559    
560              canvas.fillRect(area.x, bottom, area.w, top - bottom);
561            }
562            canvas.restore();
563          }
564        };
565      },
566      
567      addEventCallback: function(x) {
568        
569        // bail if no evets
570        if (x.events.length == 0)
571          return;
572        
573        // alias this
574        var thiz = this;
575        
576        // get attrs
577        var attrs = x.attrs;
578        
579        // check for an existing underlayCallback
580        var prevUnderlayCallback = attrs["underlayCallback"];
581        
582        // install callback
583        attrs.underlayCallback = function(canvas, area, g) {
584          
585          // call existing
586          if (prevUnderlayCallback)
587            prevUnderlayCallback(canvas, area, g);
588            
589          for (var i = 0; i < x.events.length; i++) {
590            
591            // get event and x-coordinate
592            var event = x.events[i];
593            
594            // draw line
595            canvas.save();
596            canvas.strokeStyle = event.color;
597            if (event.axis == "x") {
598              var xPos;
599              if (jQuery.isNumeric(event.pos)) {
600                xPos = g.toDomXCoord(event.pos);
601              } else {
602                xPos = thiz.normalizeDateValue(x.scale, event.pos, x.fixedtz).getTime();
603                xPos = g.toDomXCoord(xPos);
604              }
605              
606              // draw line
607              thiz.dashedLine(canvas, 
608                              xPos, 
609                              area.y, 
610                              xPos, 
611                              area.y + area.h,
612                              event.strokePattern);
613            } else if (event.axis == "y") {
614              yPos = g.toDomYCoord(event.pos);
615              
616              thiz.dashedLine(canvas, 
617                              area.x, 
618                              yPos, 
619                              area.x + area.w, 
620                              yPos,
621                              event.strokePattern);
622            }
623            canvas.restore();
624            
625            // draw label
626            if (event.label != null) {
627              canvas.save();
628              thiz.setFontSize(canvas, 12);
629              var size = canvas.measureText(event.label);
630              if (event.axis == "x") {
631                var tx = xPos - 4;
632                var ty;
633                if (event.labelLoc == "top")
634                  ty = area.y + size.width + 10;
635                else
636                  ty = area.y + area.h - 10;
637                canvas.translate(tx, ty);
638                canvas.rotate(3 * Math.PI / 2);
639                canvas.translate(-tx,-ty);
640              } else if (event.axis == "y") {
641                var ty = yPos - 4;
642                var tx;
643                if (event.labelLoc == "right")
644                  tx = area.x + area.w - size.width - 10;
645                else
646                  tx = area.x + 10;
647              }
648              canvas.fillStyle = event.color;
649              canvas.fillText(event.label, tx, ty);
650              canvas.restore();
651            }
652          }
653        };
654      },
655      
656      addDateWindowShinyInput: function(id, isDate) {
657          
658        // check for an existing drawCallback
659        var prevDrawCallback = dygraph.getOption("drawCallback");
660        
661        // install the callback
662        dygraph.updateOptions({
663          drawCallback: function(me, initial) {
664            // call existing
665            if (prevDrawCallback)
666              prevDrawCallback(me, initial);
667            // fire input change
668            var range = dygraph.xAxisRange();
669            if (isDate)
670              range = [new Date(range[0]), new Date(range[1])];
671            if (Shiny.onInputChange) // may note be ready yet in case of static render
672              Shiny.onInputChange(id + "_date_window", range); 
673          }
674        });
675      },
676      
677      addClickShinyInput: function(id, isDate) {
678        
679        var prevClickCallback = dygraph.getOption("clickCallback")
680        
681        dygraph.updateOptions({
682          clickCallback: function(e, x, points) {
683            
684            // call existing
685            if (prevClickCallback)
686              prevClickCallback(e, x, points);
687              
688			      // fire input change
689			      if (Shiny.onInputChange) { // may note be ready yet in case of static render
690              Shiny.onInputChange(el.id + "_click", {
691        				x: isDate ? new Date(x) : x,
692        				x_closest_point: isDate ? new Date(points[0].xval) : points[0].xval,
693        				y_closest_point: points[0].yval,
694        				series_name: points[0].name,
695        				'.nonce': Math.random() // Force reactivity if click hasn't changed
696  			      }); 
697			      }
698          }
699        });
700      },
701      
702      // Add dashed line support to canvas rendering context
703      // See: http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
704      dashedLine: function(canvas, x, y, x2, y2, dashArray) {
705        canvas.beginPath();
706        if (!dashArray) dashArray=[10,5];
707        if (dashLength==0) dashLength = 0.001; // Hack for Safari
708        var dashCount = dashArray.length;
709        canvas.moveTo(x, y);
710        var dx = (x2-x), dy = (y2-y);
711        var slope = dx ? dy/dx : 1e15;
712        var distRemaining = Math.sqrt( dx*dx + dy*dy );
713        var dashIndex=0, draw=true;
714        while (distRemaining>=0.1){
715          var dashLength = dashArray[dashIndex++%dashCount];
716          if (dashLength > distRemaining) dashLength = distRemaining;
717          var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
718          if (dx<0) xStep = -xStep;
719          x += xStep
720          y += slope*xStep;
721          canvas[draw ? 'lineTo' : 'moveTo'](x,y);
722          distRemaining -= dashLength;
723          draw = !draw;
724        }
725        canvas.stroke();
726      },
727      
728      setFontSize: function(canvas, size) {
729        var cFont = canvas.font;
730        var parts = cFont.split(' ');
731        if (parts.length === 2)
732          canvas.font = size + 'px ' + parts[1];
733        else if (parts.length === 3)
734          canvas.font = parts[0] + ' ' + size + 'px ' + parts[2];
735      },
736      
737      // Returns the value of a GET variable
738      queryVar: function(name) {
739        return decodeURI(window.location.search.replace(
740          new RegExp("^(?:.*[&\\?]" +
741                     encodeURI(name).replace(/[\.\+\*]/g, "\\$&") +
742                     "(?:\\=([^&]*))?)?.*$", "i"),
743          "$1"));
744      },
745      
746      // We deal exclusively in UTC dates within R, however dygraphs deal
746s 
747      // exclusively in the local time zone. Therefore, in order to plot date
748      // labels that make sense to the user when we are dealing with days,
749      // months or years we need to convert the UTC date value to a local time
750      // value that "looks like" the equivilant UTC value. To do this we add the
751      // timezone offset to the UTC date.
752      // Don't use in case of fixedtz
753      normalizeDateValue: function(scale, value, fixedtz) {
754        var date = new Date(value); 
755        if (scale != "minute" && scale != "hourly" && scale != "seconds" && !fixedtz) {
756          var localAsUTC = date.getTime() + (date.getTimezoneOffset() * 60000);
757          date = new Date(localAsUTC);
758        }
759        return date;
760      },
761      
762      // safely detect rendering on a mobile phone
763      isMobilePhone: function() {
764        try
765        {
766          return ! window.matchMedia("only screen and (min-width: 768px)").matches;
767        }
768        catch(e) {
769          return false;
770        }
771      },
772        
773      
774      resize: function(width, height) {
775        if (dygraph)
776          dygraph.resize();
777      },
778      
779      // export dygraph so other code can get a hold of it
780      dygraph: null
781    
782    };
783  },
784  
785  // track groups globally
786  groups: {}
787  
788});
789

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.