| |
| // polyfill indexOf for IE8 |
| if (!Array.prototype.indexOf) { |
| Array.prototype.indexOf = function(elt /*, from*/) { |
| var len = this.length >>> 0; |
| |
| var from = Number(arguments[1]) || 0; |
| from = (from < 0) |
| ? Math.ceil(from) |
| : Math.floor(from); |
| if (from < 0) |
| from += len; |
| |
| for (; from < len; from++) { |
| if (from in this && |
| this[from] === elt) |
| return from; |
| } |
| return -1; |
| }; |
| } |
| |
| HTMLWidgets.widget({ |
| |
| name: "dygraphs", |
| |
| type: "output", |
| |
| factory: function(el, width, height) { |
| |
| // reference to dygraph |
| var dygraph = null; |
| |
| // reference to widget global groups |
| var groups = this.groups; |
| |
| // add qt style if we are running under Qt |
| if (window.navigator.userAgent.indexOf(" Qt/") > 0) |
| el.className += " qt"; |
| |
| return { |
| |
| renderValue: function(x) { |
| |
| // reference to this for closures |
| var thiz = this; |
| |
| // get dygraph attrs and populate file field |
| var attrs = x.attrs; |
| attrs.file = x.data; |
| |
| // disable zoom interaction except for clicks |
| if (attrs.disableZoom) { |
| attrs.interactionModel = Dygraph.Interaction.nonInteractiveModel_; |
| } |
| |
| // convert non-arrays to arrays |
| for (var index = 0; index < attrs.file.length; index++) { |
| if (!$.isArray(attrs.file[index])) |
| attrs.file[index] = [].concat(attrs.file[index]); |
| } |
| |
| // resolve "auto" legend behavior |
| if (x.attrs.legend == "auto") { |
| if (x.data.length <= 2) |
| x.attrs.legend = "onmouseover"; |
| else |
| x.attrs.legend = "always"; |
| } |
| |
| if (x.format == "date") { |
| |
| // set appropriated function in case of fixed tz |
| if ((attrs.axes.x.axisLabelFormatter === undefined) && x.fixedtz) |
| attrs.axes.x.axisLabelFormatter = this.xAxisLabelFormatterFixedTZ(x.tzone); |
| |
| if ((attrs.axes.x.valueFormatter === undefined) && x.fixedtz) |
| attrs.axes.x.valueFormatter = this.xValueFormatterFixedTZ(x.scale, x.tzone); |
| |
| if ((attrs.axes.x.ticker === undefined) && x.fixedtz) |
| attrs.axes.x.ticker = this.customDateTickerFixedTZ(x.tzone); |
| |
| // provide an automatic x value formatter if none is already specified |
| if ((attrs.axes.x.valueFormatter === undefined) && (x.fixedtz != true)) |
| attrs.axes.x.valueFormatter = this.xValueFormatter(x.scale); |
| |
| // convert time to js time |
| attrs.file[0] = attrs.file[0].map(function(value) { |
| return thiz.normalizeDateValue(x.scale, value, x.fixedtz); |
| }); |
| if (attrs.dateWindow != null) { |
| attrs.dateWindow = attrs.dateWindow.map(function(value) { |
| var date = thiz.normalizeDateValue(x.scale, value, x.fixedtz); |
| return date.getTime(); |
| }); |
| } |
| } |
| |
| |
| // transpose array |
| attrs.file = HTMLWidgets.transposeArray2D(attrs.file); |
| |
| // add drawCallback for group |
| if (x.group != null) |
| this.addGroupDrawCallback(x); |
| |
| // add shading and event callback if necessary |
| this.addShadingCallback(x); |
| this.addEventCallback(x); |
| this.addZoomCallback(x); |
| |
| // disable y-axis touch events on mobile phones |
| if (attrs.mobileDisableYTouch !== false && this.isMobilePhone()) { |
| // create default interaction model if necessary |
| if (!attrs.interactionModel) |
| attrs.interactionModel = Dygraph.Interaction.defaultModel; |
| // disable y touch direction |
| attrs.interactionModel.touchstart = function(event, dygraph, context) { |
| Dygraph.defaultInteractionModel.touchstart(event, dygraph, context); |
| context.touchDirections = { x: true, y: false }; |
| }; |
| } |
| |
| // create plugins |
| if (x.plugins) { |
| attrs.plugins = []; |
| for (var plugin in x.plugins) { |
| if (x.plugins.hasOwnProperty(plugin)) { |
| |
| // get plugin options |
| var options = x.plugins[plugin]; |
| |
| // create plugin and add to dygraph |
| var p = new Dygraph.Plugins[plugin](options); |
| attrs.plugins.push(p); |
| } |
| } |
| } |
| |
| // custom plotter |
| if (x.plotter) { |
| attrs.plotter = Dygraph.Plotters[x.plotter]; |
| } |
| |
| // custom data handler |
| if (x.dataHandler) { |
| attrs.dataHandler = Dygraph.DataHandlers[x.dataHandler]; |
| } |
| |
| // custom circles |
| if (x.pointShape) { |
| if (typeof x.pointShape === 'string') { |
| attrs.drawPointCallback = Dygraph.Circles[x.pointShape.toUpperCase()]; |
| attrs.drawHighlightPointCallback = Dygraph.Circles[x.pointShape.toUpperCase()]; |
| } else { |
| for (var s in x.pointShape) { |
| if (x.pointShape.hasOwnProperty(s)) { |
| attrs.series[s].drawPointCallback = Dygraph.Circles[x.pointShape[s].toUpperCase()]; |
| attrs.series[s].drawHighlightPointCallback = Dygraph.Circles[x.pointShape[s].toUpperCase()]; |
| } |
| } |
| } |
| } |
| |
| // if there is no existing dygraph perform initialization |
| if (!dygraph) { |
| |
| // subscribe to custom shown event (fired by ioslides to trigger |
| // shiny reactivity but we can use it as well). this is necessary |
| // because if a dygraph starts out as display:none it has height |
| // and width == 0 and this doesn't change when it becomes visible |
| $(el).closest('slide').on('shown', function() { |
| if (dygraph) |
| dygraph.resize(); |
| }); |
| |
| // do the same for reveal.js |
| $(el).closest('section.slide').on('shown', function() { |
| if (dygraph) |
| dygraph.resize(); |
| }); |
| |
| // redraw on R Markdown {.tabset} tab visibility changed |
| var tab = $(el).closest('div.tab-pane'); |
| if (tab !== null) { |
| var tabID = tab.attr('id'); |
| var tabAnchor = $('a[data-toggle="tab"][href="#' + tabID + '"]'); |
| if (tabAnchor !== null) { |
| tabAnchor.on('shown.bs.tab', function() { |
| if (dygraph) |
| dygraph.resize(); |
| }); |
| } |
| } |
| // add default font for viewer mode |
| if (this.queryVar("viewer_pane") === "1") |
| document.body.style.fontFamily = "Arial, sans-serif"; |
| |
| // inject css if necessary |
| if (x.css != null) { |
| var style = document.createElement('style'); |
| style.type = 'text/css'; |
| if (style.styleSheet) |
| style.styleSheet.cssText = x.css; |
| else |
| style.appendChild(document.createTextNode(x.css)); |
| document.getElementsByTagName("head")[0].appendChild(style); |
| } |
| |
| } else { |
| |
| // retain the userDateWindow if requested |
| if (dygraph.userDateWindow != null |
| && attrs.retainDateWindow == true) { |
| attrs.dateWindow = dygraph.xAxisRange(); |
| } |
| |
| // remove it from groups if it's there |
| if (x.group != null && groups[x.group] != null) { |
| var index = groups[x.group].indexOf(dygraph); |
| if (index != -1) |
| groups[x.group].splice(index, 1); |
| } |
| |
| // destroy the existing dygraph |
| dygraph.destroy(); |
| dygraph = null; |
| } |
| |
| // create the dygraph and add it to it's group (if any) |
| dygraph = thiz.dygraph = new Dygraph(el, attrs.file, attrs); |
| dygraph.userDateWindow = attrs.dateWindow; |
| if (x.group != null) |
| groups[x.group].push(dygraph); |
| |
| // add shiny inputs for date window and click |
| if (HTMLWidgets.shinyMode) { |
| var isDate = x.format == "date"; |
| this.addClickShinyInput(el.id, isDate); |
| this.addDateWindowShinyInput(el.id, isDate); |
| } |
| |
| // set annotations |
| if (x.annotations != null) { |
| dygraph.ready(function() { |
| if (x.format == "date") { |
| x.annotations.map(function(annotation) { |
| var date = thiz.normalizeDateValue(x.scale, annotation.x, x.fixedtz); |
| annotation.x = date.getTime(); |
| }); |
| } |
| dygraph.setAnnotations(x.annotations); |
| }); |
| } |
| |
| }, |
| |
| customDateTickerFixedTZ : function(tz){ |
| return function(t,e,a,i,r) { |
| var a=Dygraph.pickDateTickGranularity(t,e,a,i); |
| if(a >= 0){ |
| |
| var n=i("axisLabelFormatter"), |
| o=i("labelsUTC"), |
| s=o?Dygraph.DateAccessorsUTC:Dygraph.DateAccessorsLocal; |
| l=Dygraph.TICK_PLACEMENT[a].datefield; |
| h=Dygraph.TICK_PLACEMENT[a].step; |
| p=Dygraph.TICK_PLACEMENT[a].spacing; |
| |
| var y = []; |
| var d = moment(t); |
| d.tz(tz); |
| d.millisecond(0); |
| |
| if(l > Dygraph.DATEFIELD_M){ |
| var x; |
| if (l === Dygraph.DATEFIELD_SS) { // seconds |
| x = d.second(); |
| d.second(x - x % h); |
| } else if(l === Dygraph.DATEFIELD_MM){ |
| d.second(0) |
| x = d.minute(); |
| d.minute(x - x % h); |
| } else if(l === Dygraph.DATEFIELD_HH){ |
| d.second(0); |
| d.minute(0); |
| x = d.hour(); |
| d.hour(x - x % h); |
| } else if(l === Dygraph.DATEFIELD_D){ |
| d.second(0); |
| d.minute(0); |
| d.hour(0); |
| if (h == 7) { // one week |
| d.startOf('week'); |
| } |
| } |
| |
| v = d.valueOf(); |
| _=moment(v).tz(tz); |
| |
| // For spacings coarser than two-hourly, we want to ignore daylight |
| // savings transitions to get consistent ticks. For finer-grained ticks, |
| // it's essential to show the DST transition in all its messiness. |
| var start_offset_min = moment(v).tz(tz).zone(); |
| var check_dst = (p >= Dygraph.TICK_PLACEMENT[Dygraph.TWO_HOURLY].spacing); |
| |
| if(a<=Dygraph.HOURLY){ |
| for(t>v&&(v+=p,_=moment(v).tz(tz));e>=v;){ |
| y.push({v:v,label:n(_,a,i,r)}); |
| v+=p; |
| _=moment(v).tz(tz); |
| } |
| }else{ |
| for(t>v&&(v+=p,_=moment(v).tz(tz));e>=v;){ |
| |
| // This ensures that we stay on the same hourly "rhythm" across |
| // daylight savings transitions. Without this, the ticks could get off |
| // by an hour. See tests/daylight-savings.html or issue 147. |
| if (check_dst && _.zone() != start_offset_min) { |
| var delta_min = _.zone() - start_offset_min; |
| v += delta_min * 60 * 1000; |
| _= moment(v).tz(tz); |
| start_offset_min = _.zone(); |
| |
| // Check whether we've backed into the previous timezone again. |
| // This can happen during a "spring forward" transition. In this case, |
| // it's best to skip this tick altogether (we may be shooting for a |
| // non-existent time like the 2AM that's skipped) and go to the next |
| // one. |
| if (moment(v + p).tz(tz).zone() != start_offset_min) { |
| v += p; |
| _= moment(v).tz(tz); |
| start_offset_min = _.zone(); |
| } |
| } |
| |
| (a>=Dygraph.DAILY||_.get('hour')%h===0)&&y.push({v:v,label:n(_,a,i,r)}); |
| v+=p; |
| _=moment(v).tz(tz); |
| } |
| } |
| }else{ |
| var start_year = moment(t).tz(tz).year(); |
| var end_year = moment(e).tz(tz).year(); |
| var start_month = moment(t).tz(tz).month(); |
| |
| if(l === Dygraph.DATEFIELD_M){ |
| var step_month = h; |
| for (var ii = start_year; ii <= end_year; ii++) { |
| for (var j = 0; j < 12;) { |
| var dt = moment(new Date(ii, j, 1)).tz(tz); |
| // fix some tz bug |
| dt.year(ii); |
| dt.month(j); |
| dt.date(1); |
| dt.hour(0); |
| v = dt.valueOf(); |
| y.push({v:v,label:n(moment(v).tz(tz),a,i,r)}); |
| j+=step_month; |
| } |
| } |
| }else{ |
| var step_year = h; |
| for (var ii = start_year; ii <= end_year;) { |
| var dt = moment(new Date(ii, 1, 1)).tz(tz); |
| // fix some tz bug |
| dt.year(ii); |
| dt.month(j); |
| dt.date(1); |
| dt.hour(0); |
| v = dt.valueOf(); |
| y.push({v:v,label:n(moment(v).tz(tz),a,i,r)}); |
| ii+=step_year; |
| } |
| } |
| } |
| return y; |
| }else{ |
| return []; |
| } |
| }; |
| }, |
| |
| xAxisLabelFormatterFixedTZ : function(tz){ |
| |
| return function dateAxisFormatter(date, granularity){ |
| var mmnt = moment(date).tz(tz); |
| if (granularity >= Dygraph.DECADAL){ |
| return mmnt.format('YYYY'); |
| }else{ |
| if(granularity >= Dygraph.MONTHLY){ |
| return mmnt.format('MMM YYYY'); |
| }else{ |
| var frac = mmnt.hour() * 3600 + mmnt.minute() * 60 + mmnt.second() + mmnt.millisecond(); |
| if (frac === 0 || granularity >= Dygraph.DAILY) { |
| return mmnt.format('DD MMM'); |
| } else { |
| if (mmnt.second()) { |
| return mmnt.format('HH:mm:ss'); |
| } else { |
| return mmnt.format('HH:mm'); |
| } |
| } |
| } |
| |
| } |
| } |
| }, |
| |
| xValueFormatterFixedTZ: function(scale, tz) { |
| |
| return function(millis) { |
| var mmnt = moment(millis).tz(tz); |
| if (scale == "yearly") |
| return mmnt.format('YYYY') + ' (' + mmnt.zoneAbbr() + ')'; |
| else if (scale == "quarterly") |
| return mmnt.fquarter(1) + ' (' + mmnt.zoneAbbr() + ')'; |
| else if (scale == "monthly") |
| return mmnt.format('MMM, YYYY')+ ' (' + mmnt.zoneAbbr() + ')'; |
| else if (scale == "daily" || scale == "weekly") |
| return mmnt.format('MMM, DD, YYYY')+ ' (' + mmnt.zoneAbbr() + ')'; |
| else |
| return mmnt.format('dddd, MMMM DD, YYYY HH:mm:ss')+ ' (' + mmnt.zoneAbbr() + ')'; |
| } |
| }, |
| |
| xValueFormatter: function(scale) { |
| |
| var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", |
| "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; |
| |
| return function(millis) { |
| var date = new Date(millis); |
| if (scale == "yearly") |
| return date.getFullYear(); |
| else if (scale == "quarterly") |
| return moment(millis).fquarter(1); |
| else if (scale == "monthly") |
| return monthNames[date.getMonth()] + ', ' + date.getFullYear(); |
| else if (scale == "daily" || scale == "weekly") |
| return monthNames[date.getMonth()] + ', ' + |
| date.getDate() + ', ' + |
| date.getFullYear(); |
| else |
| return date.toLocaleString(); |
| } |
| }, |
| |
| addZoomCallback: function(x) { |
| |
| // alias this |
| var thiz = this; |
| |
| // get attrs |
| var attrs = x.attrs; |
| |
| // check for an existing zoomCallback |
| var prevZoomCallback = attrs["zoomCallback"]; |
| |
| attrs.zoomCallback = function(minDate, maxDate, yRanges) { |
| |
| // call existing |
| if (prevZoomCallback) |
| prevZoomCallback(minDate, maxDate, yRanges); |
| |
| // record user date window (or lack thereof) |
| if (dygraph.xAxisExtremes()[0] != minDate || |
| dygraph.xAxisExtremes()[1] != maxDate) { |
| dygraph.userDateWindow = [minDate, maxDate]; |
| } else { |
| dygraph.userDateWindow = null; |
| } |
| |
| // record in group if necessary |
| if (x.group != null && groups[x.group] != null) { |
| var group = groups[x.group]; |
| for(var i = 0; i<group.length; i++) |
| group[i].userDateWindow = dygraph.userDateWindow; |
| } |
| }; |
| }, |
| |
| addGroupDrawCallback: function(x) { |
| |
| // get attrs |
| var attrs = x.attrs; |
| |
| // check for an existing drawCallback |
| var prevDrawCallback = attrs["drawCallback"]; |
| |
| groups[x.group] = groups[x.group] || []; |
| var group = groups[x.group]; |
| var blockRedraw = false; |
| attrs.drawCallback = function(me, initial) { |
| |
| // call existing |
| if (prevDrawCallback) |
| prevDrawCallback(me, initial); |
| |
| // sync peers in group |
| if (blockRedraw || initial) return; |
| blockRedraw = true; |
| var range = dygraph.xAxisRange(); |
| for (var j = 0; j < group.length; j++) { |
| if (group[j] == me) continue; |
| // update group range only if it's different (prevents |
| // infinite recursion in updateOptions) |
| var peerRange = group[j].xAxisRange(); |
| if (peerRange[0] != range[0] || peerRange[1] != range[1]) { |
| group[j].updateOptions({ |
| dateWindow: range |
| }); |
| } |
| } |
| blockRedraw = false; |
| }; |
| }, |
| |
| addShadingCallback: function(x) { |
| |
| // bail if no shadings |
| if (x.shadings.length == 0) |
| return; |
| |
| // alias this |
| var thiz = this; |
| |
| // get attrs |
| var attrs = x.attrs; |
| |
| // check for an existing underlayCallback |
| var prevUnderlayCallback = attrs["underlayCallback"]; |
| |
| // install callback |
| attrs.underlayCallback = function(canvas, area, g) { |
| |
| // call existing |
| if (prevUnderlayCallback) |
| prevUnderlayCallback(canvas, area, g); |
| |
| for (var i = 0; i < x.shadings.length; i++) { |
| var shading = x.shadings[i]; |
| canvas.save(); |
| canvas.fillStyle = shading.color; |
| if (shading.axis == "x") { |
| var x1 = shading.from; |
| var x2 = shading.to; |
| if (x.format == "date") { |
| x1 = thiz.normalizeDateValue(x.scale, x1, x.fixedtz).getTime(); |
| x2 = thiz.normalizeDateValue(x.scale, x2, x.fixedtz).getTime(); |
| } |
| var left = g.toDomXCoord(x1); |
| var right = g.toDomXCoord(x2); |
| |
| canvas.fillRect(left, area.y, right - left, area.h); |
| } else if (shading.axis == "y") { |
| var bottom = g.toDomYCoord(shading.from); |
| var top = g.toDomYCoord(shading.to); |
| |
| canvas.fillRect(area.x, bottom, area.w, top - bottom); |
| } |
| canvas.restore(); |
| } |
| }; |
| }, |
| |
| addEventCallback: function(x) { |
| |
| // bail if no evets |
| if (x.events.length == 0) |
| return; |
| |
| // alias this |
| var thiz = this; |
| |
| // get attrs |
| var attrs = x.attrs; |
| |
| // check for an existing underlayCallback |
| var prevUnderlayCallback = attrs["underlayCallback"]; |
| |
| // install callback |
| attrs.underlayCallback = function(canvas, area, g) { |
| |
| // call existing |
| if (prevUnderlayCallback) |
| prevUnderlayCallback(canvas, area, g); |
| |
| for (var i = 0; i < x.events.length; i++) { |
| |
| // get event and x-coordinate |
| var event = x.events[i]; |
| |
| // draw line |
| canvas.save(); |
| canvas.strokeStyle = event.color; |
| if (event.axis == "x") { |
| var xPos; |
| if (jQuery.isNumeric(event.pos)) { |
| xPos = g.toDomXCoord(event.pos); |
| } else { |
| xPos = thiz.normalizeDateValue(x.scale, event.pos, x.fixedtz).getTime(); |
| xPos = g.toDomXCoord(xPos); |
| } |
| |
| // draw line |
| thiz.dashedLine(canvas, |
| xPos, |
| area.y, |
| xPos, |
| area.y + area.h, |
| event.strokePattern); |
| } else if (event.axis == "y") { |
| yPos = g.toDomYCoord(event.pos); |
| |
| thiz.dashedLine(canvas, |
| area.x, |
| yPos, |
| area.x + area.w, |
| yPos, |
| event.strokePattern); |
| } |
| canvas.restore(); |
| |
| // draw label |
| if (event.label != null) { |
| canvas.save(); |
| thiz.setFontSize(canvas, 12); |
| var size = canvas.measureText(event.label); |
| if (event.axis == "x") { |
| var tx = xPos - 4; |
| var ty; |
| if (event.labelLoc == "top") |
| ty = area.y + size.width + 10; |
| else |
| ty = area.y + area.h - 10; |
| canvas.translate(tx, ty); |
| canvas.rotate(3 * Math.PI / 2); |
| canvas.translate(-tx,-ty); |
| } else if (event.axis == "y") { |
| var ty = yPos - 4; |
| var tx; |
| if (event.labelLoc == "right") |
| tx = area.x + area.w - size.width - 10; |
| else |
| tx = area.x + 10; |
| } |
| canvas.fillStyle = event.color; |
| canvas.fillText(event.label, tx, ty); |
| canvas.restore(); |
| } |
| } |
| }; |
| }, |
| |
| addDateWindowShinyInput: function(id, isDate) { |
| |
| // check for an existing drawCallback |
| var prevDrawCallback = dygraph.getOption("drawCallback"); |
| |
| // install the callback |
| dygraph.updateOptions({ |
| drawCallback: function(me, initial) { |
| // call existing |
| if (prevDrawCallback) |
| prevDrawCallback(me, initial); |
| // fire input change |
| var range = dygraph.xAxisRange(); |
| if (isDate) |
| range = [new Date(range[0]), new Date(range[1])]; |
| if (Shiny.onInputChange) // may note be ready yet in case of static render |
| Shiny.onInputChange(id + "_date_window", range); |
| } |
| }); |
| }, |
| |
| addClickShinyInput: function(id, isDate) { |
| |
| var prevClickCallback = dygraph.getOption("clickCallback") |
| |
| dygraph.updateOptions({ |
| clickCallback: function(e, x, points) { |
| |
| // call existing |
| if (prevClickCallback) |
| prevClickCallback(e, x, points); |
| |
| // fire input change |
| if (Shiny.onInputChange) { // may note be ready yet in case of static render |
| Shiny.onInputChange(el.id + "_click", { |
| x: isDate ? new Date(x) : x, |
| x_closest_point: isDate ? new Date(points[0].xval) : points[0].xval, |
| y_closest_point: points[0].yval, |
| series_name: points[0].name, |
| '.nonce': Math.random() // Force reactivity if click hasn't changed |
| }); |
| } |
| } |
| }); |
| }, |
| |
| // Add dashed line support to canvas rendering context |
| // See: http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas |
| dashedLine: function(canvas, x, y, x2, y2, dashArray) { |
| canvas.beginPath(); |
| if (!dashArray) dashArray=[10,5]; |
| if (dashLength==0) dashLength = 0.001; // Hack for Safari |
| var dashCount = dashArray.length; |
| canvas.moveTo(x, y); |
| var dx = (x2-x), dy = (y2-y); |
| var slope = dx ? dy/dx : 1e15; |
| var distRemaining = Math.sqrt( dx*dx + dy*dy ); |
| var dashIndex=0, draw=true; |
| while (distRemaining>=0.1){ |
| var dashLength = dashArray[dashIndex++%dashCount]; |
| if (dashLength > distRemaining) dashLength = distRemaining; |
| var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) ); |
| if (dx<0) xStep = -xStep; |
| x += xStep |
| y += slope*xStep; |
| canvas[draw ? 'lineTo' : 'moveTo'](x,y); |
| distRemaining -= dashLength; |
| draw = !draw; |
| } |
| canvas.stroke(); |
| }, |
| |
| setFontSize: function(canvas, size) { |
| var cFont = canvas.font; |
| var parts = cFont.split(' '); |
| if (parts.length === 2) |
| canvas.font = size + 'px ' + parts[1]; |
| else if (parts.length === 3) |
| canvas.font = parts[0] + ' ' + size + 'px ' + parts[2]; |
| }, |
| |
| // Returns the value of a GET variable |
| queryVar: function(name) { |
| return decodeURI(window.location.search.replace( |
| new RegExp("^(?:.*[&\\?]" + |
| encodeURI(name).replace(/[\.\+\*]/g, "\\$&") + |
| "(?:\\=([^&]*))?)?.*$", "i"), |
| "$1")); |
| }, |
| |
| // We deal exclusively in UTC dates within R, however dygraphs deals |
| // exclusively in the local time zone. Therefore, in order to plot date |
| // labels that make sense to the user when we are dealing with days, |
| // months or years we need to convert the UTC date value to a local time |
| // value that "looks like" the equivilant UTC value. To do this we add the |
| // timezone offset to the UTC date. |
| // Don't use in case of fixedtz |
| normalizeDateValue: function(scale, value, fixedtz) { |
| var date = new Date(value); |
| if (scale != "minute" && scale != "hourly" && scale != "seconds" && !fixedtz) { |
| var localAsUTC = date.getTime() + (date.getTimezoneOffset() * 60000); |
| date = new Date(localAsUTC); |
| } |
| return date; |
| }, |
| |
| // safely detect rendering on a mobile phone |
| isMobilePhone: function() { |
| try |
| { |
| return ! window.matchMedia("only screen and (min-width: 768px)").matches; |
| } |
| catch(e) { |
| return false; |
| } |
| }, |
| |
| |
| resize: function(width, height) { |
| if (dygraph) |
| dygraph.resize(); |
| }, |
| |
| // export dygraph so other code can get a hold of it |
| dygraph: null |
| |
| }; |
| }, |
| |
| // track groups globally |
| groups: {} |
| |
| }); |
| |