diff options
author | 2017-04-27 07:53:18 -0400 | |
---|---|---|
committer | 2017-04-27 08:31:27 -0400 | |
commit | 6beb446925c967aca92f5513adf36c5db77c00d6 (patch) | |
tree | 9392057ed0739de2445c5b2a2a8bee6dcdacbcf7 /ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins | |
parent | 246b225194e3e8dc1926294de591a94fd9787fa7 (diff) |
[PORTAL-7] Rebase
This rebasing includes common libraries and common overlays projects
abstraction of components
Change-Id: Ia1efa4deacdc5701e6205104ac021a6c80ed60ba
Signed-off-by: st782s <statta@research.att.com>
Diffstat (limited to 'ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins')
7 files changed, 2120 insertions, 0 deletions
diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/README b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/README new file mode 100644 index 00000000..f216545b --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/README @@ -0,0 +1,113 @@ +dygraphs plugins +---------------- + +A single dygraph is actually a collection of dygraphs plugins, each responsible +for some portion of the chart: the plot area, the axes, the legend, the labels, +etc. + +This forces the dygraphs code to be more modular and encourages better APIs. + +The "legend" plugin (plugins/legend.js) can be used as a template for new +plugins. + +Here is a simplified version of it, with comments to explain the plugin +lifecycle and APIs: + +---------------- + +// (standard JavaScript library wrapper; prevents polluting global namespace) +Dygraph.Plugins.Legend = (function() { + +// Plugin constructor. This is invoked once for every chart which uses the +// plugin. You can't actually access the Dygraph object at this point, so the +// initialization you do here shouldn't be chart-specific. (For that, use +// the activate() method). +var legend = function() { + this.div_ = null; +}; + +// Plugins are expected to implement this method for debugging purposes. +legend.toString = function() { + return "Legend"; +}; + +// This is called once the dygraph is ready. The chart data may not be +// available yet, but the options specified in the constructor are. +// +// Proper tasks to do here include: +// - Reading your own options +// - DOM manipulation +// - Registering event listeners +// +// "dygraph" is the Dygraph object for which this instance is being activated. +// "registerer" allows you to register event listeners. +legend.prototype.activate = function(dygraph, registerer) { + // Create the legend div and attach it to the chart DOM. + this.div_ = document.createElement("div"); + dygraph.graphDiv.appendChild(this.div_); + + // Add event listeners. These will be called whenever points are selected + // (i.e. you hover over them) or deselected (i.e. you mouse away from the + // chart). This is your only chance to register event listeners! Once this + // method returns, the gig is up. + registerer.addEventListener('select', legend.select); + registerer.addEventListener('deselect', legend.deselect); +}; + +// The functions called by event listeners all take a single parameter, an +// event object. This contains properties relevant to the particular event, but +// you can always assume that it has: +// +// 1. A "dygraph" parameter which is a reference to the chart on which the +// event took place. +// 2. A "stopPropagation" method, which you can call to prevent the event from +// being seen by any other plugins after you. This effectively cancels the +// event. +// 3. A "preventDefault" method, which prevents dygraphs from performing the +// default action associated with this event. +// +legend.select = function(e) { + // These are two of the properties specific to the "select" event object: + var xValue = e.selectedX; + var points = e.selectedPoints; + + var html = xValue + ':'; + for (var i = 0; i < points.length; i++) { + var point = points[i]; + html += ' ' + point.name + ':' + point.yval; + } + + // In an event listener, "this" refers to your plugin object. + this.div_.innerHTML = html; +}; + +// This clears out the legend when the user mouses away from the chart. +legend.deselect = function(e) { + this.div_.innerHTML = ''; +}; + +return legend; +})(); + +---------------- + +Plugin Events Reference: + +- predraw +- clearChart +- drawChart +- select +- deselect + +TODO(danvk): document all event properties for each event. + + +Special methods: +- (constructor) +- activate +- destroy + + +---------------- + +Notes on plugin registration and event cascade ordering/behavior. diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/annotations.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/annotations.js new file mode 100644 index 00000000..85761040 --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/annotations.js @@ -0,0 +1,182 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/*global Dygraph:false */ + +Dygraph.Plugins.Annotations = (function() { + +"use strict"; + +/** +Current bits of jankiness: +- Uses dygraph.layout_ to get the parsed annotations. +- Uses dygraph.plotter_.area + +It would be nice if the plugin didn't require so much special support inside +the core dygraphs classes, but annotations involve quite a bit of parsing and +layout. + +TODO(danvk): cache DOM elements. + +*/ + +var annotations = function() { + this.annotations_ = []; +}; + +annotations.prototype.toString = function() { + return "Annotations Plugin"; +}; + +annotations.prototype.activate = function(g) { + return { + clearChart: this.clearChart, + didDrawChart: this.didDrawChart + }; +}; + +annotations.prototype.detachLabels = function() { + for (var i = 0; i < this.annotations_.length; i++) { + var a = this.annotations_[i]; + if (a.parentNode) a.parentNode.removeChild(a); + this.annotations_[i] = null; + } + this.annotations_ = []; +}; + +annotations.prototype.clearChart = function(e) { + this.detachLabels(); +}; + +annotations.prototype.didDrawChart = function(e) { + var g = e.dygraph; + + // Early out in the (common) case of zero annotations. + var points = g.layout_.annotated_points; + if (!points || points.length === 0) return; + + var containerDiv = e.canvas.parentNode; + var annotationStyle = { + "position": "absolute", + "fontSize": g.getOption('axisLabelFontSize') + "px", + "zIndex": 10, + "overflow": "hidden" + }; + + var bindEvt = function(eventName, classEventName, pt) { + return function(annotation_event) { + var a = pt.annotation; + if (a.hasOwnProperty(eventName)) { + a[eventName](a, pt, g, annotation_event); + } else if (g.getOption(classEventName)) { + g.getOption(classEventName)(a, pt, g, annotation_event ); + } + }; + }; + + // Add the annotations one-by-one. + var area = e.dygraph.plotter_.area; + + // x-coord to sum of previous annotation's heights (used for stacking). + var xToUsedHeight = {}; + + for (var i = 0; i < points.length; i++) { + var p = points[i]; + if (p.canvasx < area.x || p.canvasx > area.x + area.w || + p.canvasy < area.y || p.canvasy > area.y + area.h) { + continue; + } + + var a = p.annotation; + var tick_height = 6; + if (a.hasOwnProperty("tickHeight")) { + tick_height = a.tickHeight; + } + + var div = document.createElement("div"); + for (var name in annotationStyle) { + if (annotationStyle.hasOwnProperty(name)) { + div.style[name] = annotationStyle[name]; + } + } + if (!a.hasOwnProperty('icon')) { + div.className = "dygraphDefaultAnnotation"; + } + if (a.hasOwnProperty('cssClass')) { + div.className += " " + a.cssClass; + } + + var width = a.hasOwnProperty('width') ? a.width : 16; + var height = a.hasOwnProperty('height') ? a.height : 16; + if (a.hasOwnProperty('icon')) { + var img = document.createElement("img"); + img.src = a.icon; + img.width = width; + img.height = height; + div.appendChild(img); + } else if (p.annotation.hasOwnProperty('shortText')) { + div.appendChild(document.createTextNode(p.annotation.shortText)); + } + var left = p.canvasx - width / 2; + div.style.left = left + "px"; + var divTop = 0; + if (a.attachAtBottom) { + var y = (area.y + area.h - height - tick_height); + if (xToUsedHeight[left]) { + y -= xToUsedHeight[left]; + } else { + xToUsedHeight[left] = 0; + } + xToUsedHeight[left] += (tick_height + height); + divTop = y; + } else { + divTop = p.canvasy - height - tick_height; + } + div.style.top = divTop + "px"; + div.style.width = width + "px"; + div.style.height = height + "px"; + div.title = p.annotation.text; + div.style.color = g.colorsMap_[p.name]; + div.style.borderColor = g.colorsMap_[p.name]; + a.div = div; + + g.addAndTrackEvent(div, 'click', + bindEvt('clickHandler', 'annotationClickHandler', p, this)); + g.addAndTrackEvent(div, 'mouseover', + bindEvt('mouseOverHandler', 'annotationMouseOverHandler', p, this)); + g.addAndTrackEvent(div, 'mouseout', + bindEvt('mouseOutHandler', 'annotationMouseOutHandler', p, this)); + g.addAndTrackEvent(div, 'dblclick', + bindEvt('dblClickHandler', 'annotationDblClickHandler', p, this)); + + containerDiv.appendChild(div); + this.annotations_.push(div); + + var ctx = e.drawingContext; + ctx.save(); + ctx.strokeStyle = g.colorsMap_[p.name]; + ctx.beginPath(); + if (!a.attachAtBottom) { + ctx.moveTo(p.canvasx, p.canvasy); + ctx.lineTo(p.canvasx, p.canvasy - 2 - tick_height); + } else { + var y = divTop + height; + ctx.moveTo(p.canvasx, y); + ctx.lineTo(p.canvasx, y + tick_height); + } + ctx.closePath(); + ctx.stroke(); + ctx.restore(); + } +}; + +annotations.prototype.destroy = function() { + this.detachLabels(); +}; + +return annotations; + +})(); diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/axes.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/axes.js new file mode 100644 index 00000000..cacda86e --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/axes.js @@ -0,0 +1,315 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ + +/*global Dygraph:false */ + +Dygraph.Plugins.Axes = (function() { + +"use strict"; + +/* +Bits of jankiness: +- Direct layout access +- Direct area access +- Should include calculation of ticks, not just the drawing. + +Options left to make axis-friendly. + ('axisTickSize') + ('drawAxesAtZero') + ('xAxisHeight') + +These too. What is the difference between axisLablelWidth and {x,y}AxisLabelWidth? + ('axisLabelWidth') + ('xAxisLabelWidth') + ('yAxisLabelWidth') +*/ + +/** + * Draws the axes. This includes the labels on the x- and y-axes, as well + * as the tick marks on the axes. + * It does _not_ draw the grid lines which span the entire chart. + */ +var axes = function() { + this.xlabels_ = []; + this.ylabels_ = []; +}; + +axes.prototype.toString = function() { + return "Axes Plugin"; +}; + +axes.prototype.activate = function(g) { + return { + layout: this.layout, + clearChart: this.clearChart, + willDrawChart: this.willDrawChart + }; +}; + +axes.prototype.layout = function(e) { + var g = e.dygraph; + + if (g.getOption('drawYAxis')) { + var w = g.getOption('yAxisLabelWidth') + 2 * g.getOption('axisTickSize'); + e.reserveSpaceLeft(w); + } + + if (g.getOption('drawXAxis')) { + var h; + // NOTE: I think this is probably broken now, since g.getOption() now + // hits the dictionary. (That is, g.getOption('xAxisHeight') now always + // has a value.) + if (g.getOption('xAxisHeight')) { + h = g.getOption('xAxisHeight'); + } else { + h = g.getOptionForAxis('axisLabelFontSize', 'x') + 2 * g.getOption('axisTickSize'); + } + e.reserveSpaceBottom(h); + } + + if (g.numAxes() == 2) { + // TODO(danvk): introduce a 'drawAxis' per-axis property. + if (g.getOption('drawYAxis')) { + // TODO(danvk): per-axis setting. + var w = g.getOption('yAxisLabelWidth') + 2 * g.getOption('axisTickSize'); + e.reserveSpaceRight(w); + } + } else if (g.numAxes() > 2) { + g.error("Only two y-axes are supported at this time. (Trying " + + "to use " + g.numAxes() + ")"); + } +}; + +axes.prototype.detachLabels = function() { + function removeArray(ary) { + for (var i = 0; i < ary.length; i++) { + var el = ary[i]; + if (el.parentNode) el.parentNode.removeChild(el); + } + } + + removeArray(this.xlabels_); + removeArray(this.ylabels_); + this.xlabels_ = []; + this.ylabels_ = []; +}; + +axes.prototype.clearChart = function(e) { + this.detachLabels(); +}; + +axes.prototype.willDrawChart = function(e) { + var g = e.dygraph; + if (!g.getOption('drawXAxis') && !g.getOption('drawYAxis')) return; + + // Round pixels to half-integer boundaries for crisper drawing. + function halfUp(x) { return Math.round(x) + 0.5; } + function halfDown(y){ return Math.round(y) - 0.5; } + + var context = e.drawingContext; + var containerDiv = e.canvas.parentNode; + var canvasWidth = e.canvas.width; + var canvasHeight = e.canvas.height; + + var label, x, y, tick, i; + + var makeLabelStyle = function(axis) { + return { + position: "absolute", + fontSize: g.getOptionForAxis('axisLabelFontSize', axis) + "px", + zIndex: 10, + color: g.getOptionForAxis('axisLabelColor', axis), + width: g.getOption('axisLabelWidth') + "px", + // height: g.getOptionForAxis('axisLabelFontSize', 'x') + 2 + "px", + lineHeight: "normal", // Something other than "normal" line-height screws up label positioning. + overflow: "hidden" + }; + }; + + var labelStyles = { + x : makeLabelStyle('x'), + y : makeLabelStyle('y'), + y2 : makeLabelStyle('y2') + }; + + var makeDiv = function(txt, axis, prec_axis) { + /* + * This seems to be called with the following three sets of axis/prec_axis: + * x: undefined + * y: y1 + * y: y2 + */ + var div = document.createElement("div"); + var labelStyle = labelStyles[prec_axis == 'y2' ? 'y2' : axis]; + for (var name in labelStyle) { + if (labelStyle.hasOwnProperty(name)) { + div.style[name] = labelStyle[name]; + } + } + var inner_div = document.createElement("div"); + inner_div.className = 'dygraph-axis-label' + + ' dygraph-axis-label-' + axis + + (prec_axis ? ' dygraph-axis-label-' + prec_axis : ''); + inner_div.innerHTML = txt; + div.appendChild(inner_div); + return div; + }; + + // axis lines + context.save(); + + var layout = g.layout_; + var area = e.dygraph.plotter_.area; + + if (g.getOption('drawYAxis')) { + if (layout.yticks && layout.yticks.length > 0) { + var num_axes = g.numAxes(); + for (i = 0; i < layout.yticks.length; i++) { + tick = layout.yticks[i]; + if (typeof(tick) == "function") return; + x = area.x; + var sgn = 1; + var prec_axis = 'y1'; + if (tick[0] == 1) { // right-side y-axis + x = area.x + area.w; + sgn = -1; + prec_axis = 'y2'; + } + var fontSize = g.getOptionForAxis('axisLabelFontSize', prec_axis); + y = area.y + tick[1] * area.h; + + /* Tick marks are currently clipped, so don't bother drawing them. + context.beginPath(); + context.moveTo(halfUp(x), halfDown(y)); + context.lineTo(halfUp(x - sgn * this.attr_('axisTickSize')), halfDown(y)); + context.closePath(); + context.stroke(); + */ + + label = makeDiv(tick[2], 'y', num_axes == 2 ? prec_axis : null); + var top = (y - fontSize / 2); + if (top < 0) top = 0; + + if (top + fontSize + 3 > canvasHeight) { + label.style.bottom = "0px"; + } else { + label.style.top = top + "px"; + } + if (tick[0] === 0) { + label.style.left = (area.x - g.getOption('yAxisLabelWidth') - g.getOption('axisTickSize')) + "px"; + label.style.textAlign = "right"; + } else if (tick[0] == 1) { + label.style.left = (area.x + area.w + + g.getOption('axisTickSize')) + "px"; + label.style.textAlign = "left"; + } + label.style.width = g.getOption('yAxisLabelWidth') + "px"; + containerDiv.appendChild(label); + this.ylabels_.push(label); + } + + // The lowest tick on the y-axis often overlaps with the leftmost + // tick on the x-axis. Shift the bottom tick up a little bit to + // compensate if necessary. + var bottomTick = this.ylabels_[0]; + // Interested in the y2 axis also? + var fontSize = g.getOptionForAxis('axisLabelFontSize', "y"); + var bottom = parseInt(bottomTick.style.top, 10) + fontSize; + if (bottom > canvasHeight - fontSize) { + bottomTick.style.top = (parseInt(bottomTick.style.top, 10) - + fontSize / 2) + "px"; + } + } + + // draw a vertical line on the left to separate the chart from the labels. + var axisX; + if (g.getOption('drawAxesAtZero')) { + var r = g.toPercentXCoord(0); + if (r > 1 || r < 0 || isNaN(r)) r = 0; + axisX = halfUp(area.x + r * area.w); + } else { + axisX = halfUp(area.x); + } + + context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y'); + context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y'); + + context.beginPath(); + context.moveTo(axisX, halfDown(area.y)); + context.lineTo(axisX, halfDown(area.y + area.h)); + context.closePath(); + context.stroke(); + + // if there's a secondary y-axis, draw a vertical line for that, too. + if (g.numAxes() == 2) { + context.strokeStyle = g.getOptionForAxis('axisLineColor', 'y2'); + context.lineWidth = g.getOptionForAxis('axisLineWidth', 'y2'); + context.beginPath(); + context.moveTo(halfDown(area.x + area.w), halfDown(area.y)); + context.lineTo(halfDown(area.x + area.w), halfDown(area.y + area.h)); + context.closePath(); + context.stroke(); + } + } + + if (g.getOption('drawXAxis')) { + if (layout.xticks) { + for (i = 0; i < layout.xticks.length; i++) { + tick = layout.xticks[i]; + x = area.x + tick[0] * area.w; + y = area.y + area.h; + + /* Tick marks are currently clipped, so don't bother drawing them. + context.beginPath(); + context.moveTo(halfUp(x), halfDown(y)); + context.lineTo(halfUp(x), halfDown(y + this.attr_('axisTickSize'))); + context.closePath(); + context.stroke(); + */ + + label = makeDiv(tick[1], 'x'); + label.style.textAlign = "center"; + label.style.top = (y + g.getOption('axisTickSize')) + 'px'; + + var left = (x - g.getOption('axisLabelWidth')/2); + if (left + g.getOption('axisLabelWidth') > canvasWidth) { + left = canvasWidth - g.getOption('xAxisLabelWidth'); + label.style.textAlign = "right"; + } + if (left < 0) { + left = 0; + label.style.textAlign = "left"; + } + + label.style.left = left + "px"; + label.style.width = g.getOption('xAxisLabelWidth') + "px"; + containerDiv.appendChild(label); + this.xlabels_.push(label); + } + } + + context.strokeStyle = g.getOptionForAxis('axisLineColor', 'x'); + context.lineWidth = g.getOptionForAxis('axisLineWidth', 'x'); + context.beginPath(); + var axisY; + if (g.getOption('drawAxesAtZero')) { + var r = g.toPercentYCoord(0, 0); + if (r > 1 || r < 0) r = 1; + axisY = halfDown(area.y + r * area.h); + } else { + axisY = halfDown(area.y + area.h); + } + context.moveTo(halfUp(area.x), axisY); + context.lineTo(halfUp(area.x + area.w), axisY); + context.closePath(); + context.stroke(); + } + + context.restore(); +}; + +return axes; +})(); diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/chart-labels.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/chart-labels.js new file mode 100644 index 00000000..da231d80 --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/chart-labels.js @@ -0,0 +1,202 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false */ + +Dygraph.Plugins.ChartLabels = (function() { + +"use strict"; + +// TODO(danvk): move chart label options out of dygraphs and into the plugin. +// TODO(danvk): only tear down & rebuild the DIVs when it's necessary. + +var chart_labels = function() { + this.title_div_ = null; + this.xlabel_div_ = null; + this.ylabel_div_ = null; + this.y2label_div_ = null; +}; + +chart_labels.prototype.toString = function() { + return "ChartLabels Plugin"; +}; + +chart_labels.prototype.activate = function(g) { + return { + layout: this.layout, + // clearChart: this.clearChart, + didDrawChart: this.didDrawChart + }; +}; + +// QUESTION: should there be a plugin-utils.js? +var createDivInRect = function(r) { + var div = document.createElement('div'); + div.style.position = 'absolute'; + div.style.left = r.x + 'px'; + div.style.top = r.y + 'px'; + div.style.width = r.w + 'px'; + div.style.height = r.h + 'px'; + return div; +}; + +// Detach and null out any existing nodes. +chart_labels.prototype.detachLabels_ = function() { + var els = [ this.title_div_, + this.xlabel_div_, + this.ylabel_div_, + this.y2label_div_ ]; + for (var i = 0; i < els.length; i++) { + var el = els[i]; + if (!el) continue; + if (el.parentNode) el.parentNode.removeChild(el); + } + + this.title_div_ = null; + this.xlabel_div_ = null; + this.ylabel_div_ = null; + this.y2label_div_ = null; +}; + +var createRotatedDiv = function(g, box, axis, classes, html) { + // TODO(danvk): is this outer div actually necessary? + var div = document.createElement("div"); + div.style.position = 'absolute'; + if (axis == 1) { + // NOTE: this is cheating. Should be positioned relative to the box. + div.style.left = '0px'; + } else { + div.style.left = box.x + 'px'; + } + div.style.top = box.y + 'px'; + div.style.width = box.w + 'px'; + div.style.height = box.h + 'px'; + div.style.fontSize = (g.getOption('yLabelWidth') - 2) + 'px'; + + var inner_div = document.createElement("div"); + inner_div.style.position = 'absolute'; + inner_div.style.width = box.h + 'px'; + inner_div.style.height = box.w + 'px'; + inner_div.style.top = (box.h / 2 - box.w / 2) + 'px'; + inner_div.style.left = (box.w / 2 - box.h / 2) + 'px'; + inner_div.style.textAlign = 'center'; + + // CSS rotation is an HTML5 feature which is not standardized. Hence every + // browser has its own name for the CSS style. + var val = 'rotate(' + (axis == 1 ? '-' : '') + '90deg)'; + inner_div.style.transform = val; // HTML5 + inner_div.style.WebkitTransform = val; // Safari/Chrome + inner_div.style.MozTransform = val; // Firefox + inner_div.style.OTransform = val; // Opera + inner_div.style.msTransform = val; // IE9 + + if (typeof(document.documentMode) !== 'undefined' && + document.documentMode < 9) { + // We're dealing w/ an old version of IE, so we have to rotate the text + // using a BasicImage transform. This uses a different origin of rotation + // than HTML5 rotation (top left of div vs. its center). + inner_div.style.filter = + 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + + (axis == 1 ? '3' : '1') + ')'; + inner_div.style.left = '0px'; + inner_div.style.top = '0px'; + } + + var class_div = document.createElement("div"); + class_div.className = classes; + class_div.innerHTML = html; + + inner_div.appendChild(class_div); + div.appendChild(inner_div); + return div; +}; + +chart_labels.prototype.layout = function(e) { + this.detachLabels_(); + + var g = e.dygraph; + var div = e.chart_div; + if (g.getOption('title')) { + // QUESTION: should this return an absolutely-positioned div instead? + var title_rect = e.reserveSpaceTop(g.getOption('titleHeight')); + this.title_div_ = createDivInRect(title_rect); + this.title_div_.style.textAlign = 'center'; + this.title_div_.style.fontSize = (g.getOption('titleHeight') - 8) + 'px'; + this.title_div_.style.fontWeight = 'bold'; + this.title_div_.style.zIndex = 10; + + var class_div = document.createElement("div"); + class_div.className = 'dygraph-label dygraph-title'; + class_div.innerHTML = g.getOption('title'); + this.title_div_.appendChild(class_div); + div.appendChild(this.title_div_); + } + + if (g.getOption('xlabel')) { + var x_rect = e.reserveSpaceBottom(g.getOption('xLabelHeight')); + this.xlabel_div_ = createDivInRect(x_rect); + this.xlabel_div_.style.textAlign = 'center'; + this.xlabel_div_.style.fontSize = (g.getOption('xLabelHeight') - 2) + 'px'; + + var class_div = document.createElement("div"); + class_div.className = 'dygraph-label dygraph-xlabel'; + class_div.innerHTML = g.getOption('xlabel'); + this.xlabel_div_.appendChild(class_div); + div.appendChild(this.xlabel_div_); + } + + if (g.getOption('ylabel')) { + // It would make sense to shift the chart here to make room for the y-axis + // label, but the default yAxisLabelWidth is large enough that this results + // in overly-padded charts. The y-axis label should fit fine. If it + // doesn't, the yAxisLabelWidth option can be increased. + var y_rect = e.reserveSpaceLeft(0); + + this.ylabel_div_ = createRotatedDiv( + g, y_rect, + 1, // primary (left) y-axis + 'dygraph-label dygraph-ylabel', + g.getOption('ylabel')); + div.appendChild(this.ylabel_div_); + } + + if (g.getOption('y2label') && g.numAxes() == 2) { + // same logic applies here as for ylabel. + var y2_rect = e.reserveSpaceRight(0); + this.y2label_div_ = createRotatedDiv( + g, y2_rect, + 2, // secondary (right) y-axis + 'dygraph-label dygraph-y2label', + g.getOption('y2label')); + div.appendChild(this.y2label_div_); + } +}; + +chart_labels.prototype.didDrawChart = function(e) { + var g = e.dygraph; + if (this.title_div_) { + this.title_div_.children[0].innerHTML = g.getOption('title'); + } + if (this.xlabel_div_) { + this.xlabel_div_.children[0].innerHTML = g.getOption('xlabel'); + } + if (this.ylabel_div_) { + this.ylabel_div_.children[0].children[0].innerHTML = g.getOption('ylabel'); + } + if (this.y2label_div_) { + this.y2label_div_.children[0].children[0].innerHTML = g.getOption('y2label'); + } +}; + +chart_labels.prototype.clearChart = function() { +}; + +chart_labels.prototype.destroy = function() { + this.detachLabels_(); +}; + + +return chart_labels; +})(); diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/grid.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/grid.js new file mode 100644 index 00000000..425d93f6 --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/grid.js @@ -0,0 +1,124 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false */ + +Dygraph.Plugins.Grid = (function() { + +/* + +Current bits of jankiness: +- Direct layout access +- Direct area access + +*/ + +"use strict"; + + +/** + * Draws the gridlines, i.e. the gray horizontal & vertical lines running the + * length of the chart. + * + * @constructor + */ +var grid = function() { +}; + +grid.prototype.toString = function() { + return "Gridline Plugin"; +}; + +grid.prototype.activate = function(g) { + return { + willDrawChart: this.willDrawChart + }; +}; + +grid.prototype.willDrawChart = function(e) { + // Draw the new X/Y grid. Lines appear crisper when pixels are rounded to + // half-integers. This prevents them from drawing in two rows/cols. + var g = e.dygraph; + var ctx = e.drawingContext; + var layout = g.layout_; + var area = e.dygraph.plotter_.area; + + function halfUp(x) { return Math.round(x) + 0.5; } + function halfDown(y){ return Math.round(y) - 0.5; } + + var x, y, i, ticks; + if (g.getOption('drawYGrid')) { + var axes = ["y", "y2"]; + var strokeStyles = [], lineWidths = [], drawGrid = [], stroking = [], strokePattern = []; + for (var i = 0; i < axes.length; i++) { + drawGrid[i] = g.getOptionForAxis("drawGrid", axes[i]); + if (drawGrid[i]) { + strokeStyles[i] = g.getOptionForAxis('gridLineColor', axes[i]); + lineWidths[i] = g.getOptionForAxis('gridLineWidth', axes[i]); + strokePattern[i] = g.getOptionForAxis('gridLinePattern', axes[i]); + stroking[i] = strokePattern[i] && (strokePattern[i].length >= 2); + } + } + ticks = layout.yticks; + ctx.save(); + // draw grids for the different y axes + for (i = 0; i < ticks.length; i++) { + var axis = ticks[i][0]; + if(drawGrid[axis]) { + if (stroking[axis]) { + ctx.installPattern(strokePattern[axis]); + } + ctx.strokeStyle = strokeStyles[axis]; + ctx.lineWidth = lineWidths[axis]; + + x = halfUp(area.x); + y = halfDown(area.y + ticks[i][1] * area.h); + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x + area.w, y); + ctx.closePath(); + ctx.stroke(); + + if (stroking[axis]) { + ctx.uninstallPattern(); + } + } + } + ctx.restore(); + } + + // draw grid for x axis + if (g.getOption('drawXGrid') && g.getOptionForAxis("drawGrid", 'x')) { + ticks = layout.xticks; + ctx.save(); + var strokePattern = g.getOptionForAxis('gridLinePattern', 'x'); + var stroking = strokePattern && (strokePattern.length >= 2); + if (stroking) { + ctx.installPattern(strokePattern); + } + ctx.strokeStyle = g.getOptionForAxis('gridLineColor', 'x'); + ctx.lineWidth = g.getOptionForAxis('gridLineWidth', 'x'); + for (i = 0; i < ticks.length; i++) { + x = halfUp(area.x + ticks[i][0] * area.w); + y = halfDown(area.y + area.h); + ctx.beginPath(); + ctx.moveTo(x, y); + ctx.lineTo(x, area.y); + ctx.closePath(); + ctx.stroke(); + } + if (stroking) { + ctx.uninstallPattern(); + } + ctx.restore(); + } +}; + +grid.prototype.destroy = function() { +}; + +return grid; + +})(); diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/legend.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/legend.js new file mode 100644 index 00000000..7406f82a --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/legend.js @@ -0,0 +1,332 @@ +/** + * @license + * Copyright 2012 Dan Vanderkam (danvdk@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false */ + +Dygraph.Plugins.Legend = (function() { +/* + +Current bits of jankiness: +- Uses two private APIs: + 1. Dygraph.optionsViewForAxis_ + 2. dygraph.plotter_.area +- Registers for a "predraw" event, which should be renamed. +- I call calculateEmWidthInDiv more often than needed. + +*/ + +/*jshint globalstrict: true */ +/*global Dygraph:false */ +"use strict"; + + +/** + * Creates the legend, which appears when the user hovers over the chart. + * The legend can be either a user-specified or generated div. + * + * @constructor + */ +var legend = function() { + this.legend_div_ = null; + this.is_generated_div_ = false; // do we own this div, or was it user-specified? +}; + +legend.prototype.toString = function() { + return "Legend Plugin"; +}; + +// (defined below) +var generateLegendHTML, generateLegendDashHTML; + +/** + * This is called during the dygraph constructor, after options have been set + * but before the data is available. + * + * Proper tasks to do here include: + * - Reading your own options + * - DOM manipulation + * - Registering event listeners + * + * @param {Dygraph} g Graph instance. + * @return {object.<string, function(ev)>} Mapping of event names to callbacks. + */ +legend.prototype.activate = function(g) { + var div; + var divWidth = g.getOption('labelsDivWidth'); + + var userLabelsDiv = g.getOption('labelsDiv'); + if (userLabelsDiv && null !== userLabelsDiv) { + if (typeof(userLabelsDiv) == "string" || userLabelsDiv instanceof String) { + div = document.getElementById(userLabelsDiv); + } else { + div = userLabelsDiv; + } + } else { + // Default legend styles. These can be overridden in CSS by adding + // "!important" after your rule, e.g. "left: 30px !important;" + var messagestyle = { + "position": "absolute", + "fontSize": "14px", + "zIndex": 10, + "width": divWidth + "px", + "top": "0px", + "left": (g.size().width - divWidth - 2) + "px", + "background": "white", + "lineHeight": "normal", + "textAlign": "left", + "overflow": "hidden"}; + + // TODO(danvk): get rid of labelsDivStyles? CSS is better. + Dygraph.update(messagestyle, g.getOption('labelsDivStyles')); + div = document.createElement("div"); + div.className = "dygraph-legend"; + for (var name in messagestyle) { + if (!messagestyle.hasOwnProperty(name)) continue; + + try { + div.style[name] = messagestyle[name]; + } catch (e) { + this.warn("You are using unsupported css properties for your " + + "browser in labelsDivStyles"); + } + } + + // TODO(danvk): come up with a cleaner way to expose this. + g.graphDiv.appendChild(div); + this.is_generated_div_ = true; + } + + this.legend_div_ = div; + this.one_em_width_ = 10; // just a guess, will be updated. + + return { + select: this.select, + deselect: this.deselect, + // TODO(danvk): rethink the name "predraw" before we commit to it in any API. + predraw: this.predraw, + didDrawChart: this.didDrawChart + }; +}; + +// Needed for dashed lines. +var calculateEmWidthInDiv = function(div) { + var sizeSpan = document.createElement('span'); + sizeSpan.setAttribute('style', 'margin: 0; padding: 0 0 0 1em; border: 0;'); + div.appendChild(sizeSpan); + var oneEmWidth=sizeSpan.offsetWidth; + div.removeChild(sizeSpan); + return oneEmWidth; +}; + +legend.prototype.select = function(e) { + var xValue = e.selectedX; + var points = e.selectedPoints; + + var html = generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_); + this.legend_div_.innerHTML = html; +}; + +legend.prototype.deselect = function(e) { + // Have to do this every time, since styles might have changed. + var oneEmWidth = calculateEmWidthInDiv(this.legend_div_); + this.one_em_width_ = oneEmWidth; + + var html = generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth); + this.legend_div_.innerHTML = html; +}; + +legend.prototype.didDrawChart = function(e) { + this.deselect(e); +}; + +// Right edge should be flush with the right edge of the charting area (which +// may not be the same as the right edge of the div, if we have two y-axes. +// TODO(danvk): is any of this really necessary? Could just set "right" in "activate". +/** + * Position the labels div so that: + * - its right edge is flush with the right edge of the charting area + * - its top edge is flush with the top edge of the charting area + * @private + */ +legend.prototype.predraw = function(e) { + // Don't touch a user-specified labelsDiv. + if (!this.is_generated_div_) return; + + // TODO(danvk): only use real APIs for this. + e.dygraph.graphDiv.appendChild(this.legend_div_); + var area = e.dygraph.plotter_.area; + var labelsDivWidth = e.dygraph.getOption("labelsDivWidth"); + this.legend_div_.style.left = area.x + area.w - labelsDivWidth - 1 + "px"; + this.legend_div_.style.top = area.y + "px"; + this.legend_div_.style.width = labelsDivWidth + "px"; +}; + +/** + * Called when dygraph.destroy() is called. + * You should null out any references and detach any DOM elements. + */ +legend.prototype.destroy = function() { + this.legend_div_ = null; +}; + +/** + * @private + * Generates HTML for the legend which is displayed when hovering over the + * chart. If no selected points are specified, a default legend is returned + * (this may just be the empty string). + * @param { Number } [x] The x-value of the selected points. + * @param { [Object] } [sel_points] List of selected points for the given + * x-value. Should have properties like 'name', 'yval' and 'canvasy'. + * @param { Number } [oneEmWidth] The pixel width for 1em in the legend. Only + * relevant when displaying a legend with no selection (i.e. {legend: + * 'always'}) and with dashed lines. + */ +generateLegendHTML = function(g, x, sel_points, oneEmWidth) { + // TODO(danvk): deprecate this option in place of {legend: 'never'} + if (g.getOption('showLabelsOnHighlight') !== true) return ''; + + // If no points are selected, we display a default legend. Traditionally, + // this has been blank. But a better default would be a conventional legend, + // which provides essential information for a non-interactive chart. + var html, sepLines, i, dash, strokePattern; + var labels = g.getLabels(); + + if (typeof(x) === 'undefined') { + if (g.getOption('legend') != 'always') { + return ''; + } + + sepLines = g.getOption('labelsSeparateLines'); + html = ''; + for (i = 1; i < labels.length; i++) { + var series = g.getPropertiesForSeries(labels[i]); + if (!series.visible) continue; + + if (html !== '') html += (sepLines ? '<br/>' : ' '); + strokePattern = g.getOption("strokePattern", labels[i]); + dash = generateLegendDashHTML(strokePattern, series.color, oneEmWidth); + html += "<span style='font-weight: bold; color: " + series.color + ";'>" + + dash + " " + labels[i] + "</span>"; + } + return html; + } + + // TODO(danvk): remove this use of a private API + var xOptView = g.optionsViewForAxis_('x'); + var xvf = xOptView('valueFormatter'); + html = xvf(x, xOptView, labels[0], g); + if (html !== '') { + html += ':'; + } + + var yOptViews = []; + var num_axes = g.numAxes(); + for (i = 0; i < num_axes; i++) { + // TODO(danvk): remove this use of a private API + yOptViews[i] = g.optionsViewForAxis_('y' + (i ? 1 + i : '')); + } + var showZeros = g.getOption("labelsShowZeroValues"); + sepLines = g.getOption("labelsSeparateLines"); + var highlightSeries = g.getHighlightSeries(); + for (i = 0; i < sel_points.length; i++) { + var pt = sel_points[i]; + if (pt.yval === 0 && !showZeros) continue; + if (!Dygraph.isOK(pt.canvasy)) continue; + if (sepLines) html += "<br/>"; + + var series = g.getPropertiesForSeries(pt.name); + var yOptView = yOptViews[series.axis - 1]; + var fmtFunc = yOptView('valueFormatter'); + var yval = fmtFunc(pt.yval, yOptView, pt.name, g); + + var cls = (pt.name == highlightSeries) ? " class='highlight'" : ""; + + // TODO(danvk): use a template string here and make it an attribute. + html += "<span" + cls + ">" + " <b><span style='color: " + series.color + ";'>" + + pt.name + "</span></b>: " + yval + "</span>"; + } + return html; +}; + + +/** + * Generates html for the "dash" displayed on the legend when using "legend: always". + * In particular, this works for dashed lines with any stroke pattern. It will + * try to scale the pattern to fit in 1em width. Or if small enough repeat the + * pattern for 1em width. + * + * @param strokePattern The pattern + * @param color The color of the series. + * @param oneEmWidth The width in pixels of 1em in the legend. + * @private + */ +generateLegendDashHTML = function(strokePattern, color, oneEmWidth) { + // IE 7,8 fail at these divs, so they get boring legend, have not tested 9. + var isIE = (/MSIE/.test(navigator.userAgent) && !window.opera); + if (isIE) return "—"; + + // Easy, common case: a solid line + if (!strokePattern || strokePattern.length <= 1) { + return "<div style=\"display: inline-block; position: relative; " + + "bottom: .5ex; padding-left: 1em; height: 1px; " + + "border-bottom: 2px solid " + color + ";\"></div>"; + } + + var i, j, paddingLeft, marginRight; + var strokePixelLength = 0, segmentLoop = 0; + var normalizedPattern = []; + var loop; + + // Compute the length of the pixels including the first segment twice, + // since we repeat it. + for (i = 0; i <= strokePattern.length; i++) { + strokePixelLength += strokePattern[i%strokePattern.length]; + } + + // See if we can loop the pattern by itself at least twice. + loop = Math.floor(oneEmWidth/(strokePixelLength-strokePattern[0])); + if (loop > 1) { + // This pattern fits at least two times, no scaling just convert to em; + for (i = 0; i < strokePattern.length; i++) { + normalizedPattern[i] = strokePattern[i]/oneEmWidth; + } + // Since we are repeating the pattern, we don't worry about repeating the + // first segment in one draw. + segmentLoop = normalizedPattern.length; + } else { + // If the pattern doesn't fit in the legend we scale it to fit. + loop = 1; + for (i = 0; i < strokePattern.length; i++) { + normalizedPattern[i] = strokePattern[i]/strokePixelLength; + } + // For the scaled patterns we do redraw the first segment. + segmentLoop = normalizedPattern.length+1; + } + + // Now make the pattern. + var dash = ""; + for (j = 0; j < loop; j++) { + for (i = 0; i < segmentLoop; i+=2) { + // The padding is the drawn segment. + paddingLeft = normalizedPattern[i%normalizedPattern.length]; + if (i < strokePattern.length) { + // The margin is the space segment. + marginRight = normalizedPattern[(i+1)%normalizedPattern.length]; + } else { + // The repeated first segment has no right margin. + marginRight = 0; + } + dash += "<div style=\"display: inline-block; position: relative; " + + "bottom: .5ex; margin-right: " + marginRight + "em; padding-left: " + + paddingLeft + "em; height: 1px; border-bottom: 2px solid " + color + + ";\"></div>"; + } + } + return dash; +}; + + +return legend; +})(); diff --git a/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/range-selector.js b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/range-selector.js new file mode 100644 index 00000000..089e64aa --- /dev/null +++ b/ecomp-sdk/epsdk-app-overlay/src/main/webapp/static/fusion/raptor/dy3/js/plugins/range-selector.js @@ -0,0 +1,852 @@ +/** + * @license + * Copyright 2011 Paul Felix (paul.eric.felix@gmail.com) + * MIT-licensed (http://opensource.org/licenses/MIT) + */ +/*global Dygraph:false,TouchEvent:false */ + +/** + * @fileoverview This file contains the RangeSelector plugin used to provide + * a timeline range selector widget for dygraphs. + */ + +Dygraph.Plugins.RangeSelector = (function() { + +/*jshint globalstrict: true */ +/*global Dygraph:false */ +"use strict"; + +var rangeSelector = function() { + this.isIE_ = /MSIE/.test(navigator.userAgent) && !window.opera; + this.hasTouchInterface_ = typeof(TouchEvent) != 'undefined'; + this.isMobileDevice_ = /mobile|android/gi.test(navigator.appVersion); + this.interfaceCreated_ = false; +}; + +rangeSelector.prototype.toString = function() { + return "RangeSelector Plugin"; +}; + +rangeSelector.prototype.activate = function(dygraph) { + this.dygraph_ = dygraph; + this.isUsingExcanvas_ = dygraph.isUsingExcanvas_; + if (this.getOption_('showRangeSelector')) { + this.createInterface_(); + } + return { + layout: this.reserveSpace_, + predraw: this.renderStaticLayer_, + didDrawChart: this.renderInteractiveLayer_ + }; +}; + +rangeSelector.prototype.destroy = function() { + this.bgcanvas_ = null; + this.fgcanvas_ = null; + this.leftZoomHandle_ = null; + this.rightZoomHandle_ = null; + this.iePanOverlay_ = null; +}; + +//------------------------------------------------------------------ +// Private methods +//------------------------------------------------------------------ + +rangeSelector.prototype.getOption_ = function(name) { + return this.dygraph_.getOption(name); +}; + +rangeSelector.prototype.setDefaultOption_ = function(name, value) { + return this.dygraph_.attrs_[name] = value; +}; + +/** + * @private + * Creates the range selector elements and adds them to the graph. + */ +rangeSelector.prototype.createInterface_ = function() { + this.createCanvases_(); + if (this.isUsingExcanvas_) { + this.createIEPanOverlay_(); + } + this.createZoomHandles_(); + this.initInteraction_(); + + // Range selector and animatedZooms have a bad interaction. See issue 359. + if (this.getOption_('animatedZooms')) { + this.dygraph_.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.'); + this.dygraph_.updateOptions({animatedZooms: false}, true); + } + + this.interfaceCreated_ = true; + this.addToGraph_(); +}; + +/** + * @private + * Adds the range selector to the graph. + */ +rangeSelector.prototype.addToGraph_ = function() { + var graphDiv = this.graphDiv_ = this.dygraph_.graphDiv; + graphDiv.appendChild(this.bgcanvas_); + graphDiv.appendChild(this.fgcanvas_); + graphDiv.appendChild(this.leftZoomHandle_); + graphDiv.appendChild(this.rightZoomHandle_); +}; + +/** + * @private + * Removes the range selector from the graph. + */ +rangeSelector.prototype.removeFromGraph_ = function() { + var graphDiv = this.graphDiv_; + graphDiv.removeChild(this.bgcanvas_); + graphDiv.removeChild(this.fgcanvas_); + graphDiv.removeChild(this.leftZoomHandle_); + graphDiv.removeChild(this.rightZoomHandle_); + this.graphDiv_ = null; +}; + +/** + * @private + * Called by Layout to allow range selector to reserve its space. + */ +rangeSelector.prototype.reserveSpace_ = function(e) { + if (this.getOption_('showRangeSelector')) { + e.reserveSpaceBottom(this.getOption_('rangeSelectorHeight') + 4); + } +}; + +/** + * @private + * Renders the static portion of the range selector at the predraw stage. + */ +rangeSelector.prototype.renderStaticLayer_ = function() { + if (!this.updateVisibility_()) { + return; + } + this.resize_(); + this.drawStaticLayer_(); +}; + +/** + * @private + * Renders the interactive portion of the range selector after the chart has been drawn. + */ +rangeSelector.prototype.renderInteractiveLayer_ = function() { + if (!this.updateVisibility_() || this.isChangingRange_) { + return; + } + this.placeZoomHandles_(); + this.drawInteractiveLayer_(); +}; + +/** + * @private + * Check to see if the range selector is enabled/disabled and update visibility accordingly. + */ +rangeSelector.prototype.updateVisibility_ = function() { + var enabled = this.getOption_('showRangeSelector'); + if (enabled) { + if (!this.interfaceCreated_) { + this.createInterface_(); + } else if (!this.graphDiv_ || !this.graphDiv_.parentNode) { + this.addToGraph_(); + } + } else if (this.graphDiv_) { + this.removeFromGraph_(); + var dygraph = this.dygraph_; + setTimeout(function() { dygraph.width_ = 0; dygraph.resize(); }, 1); + } + return enabled; +}; + +/** + * @private + * Resizes the range selector. + */ +rangeSelector.prototype.resize_ = function() { + function setElementRect(canvas, rect) { + canvas.style.top = rect.y + 'px'; + canvas.style.left = rect.x + 'px'; + canvas.width = rect.w; + canvas.height = rect.h; + canvas.style.width = canvas.width + 'px'; // for IE + canvas.style.height = canvas.height + 'px'; // for IE + } + + var plotArea = this.dygraph_.layout_.getPlotArea(); + + var xAxisLabelHeight = 0; + if(this.getOption_('drawXAxis')){ + xAxisLabelHeight = this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize')); + } + this.canvasRect_ = { + x: plotArea.x, + y: plotArea.y + plotArea.h + xAxisLabelHeight + 4, + w: plotArea.w, + h: this.getOption_('rangeSelectorHeight') + }; + + setElementRect(this.bgcanvas_, this.canvasRect_); + setElementRect(this.fgcanvas_, this.canvasRect_); +}; + +/** + * @private + * Creates the background and foreground canvases. + */ +rangeSelector.prototype.createCanvases_ = function() { + this.bgcanvas_ = Dygraph.createCanvas(); + this.bgcanvas_.className = 'dygraph-rangesel-bgcanvas'; + this.bgcanvas_.style.position = 'absolute'; + this.bgcanvas_.style.zIndex = 9; + this.bgcanvas_ctx_ = Dygraph.getContext(this.bgcanvas_); + + this.fgcanvas_ = Dygraph.createCanvas(); + this.fgcanvas_.className = 'dygraph-rangesel-fgcanvas'; + this.fgcanvas_.style.position = 'absolute'; + this.fgcanvas_.style.zIndex = 9; + this.fgcanvas_.style.cursor = 'default'; + this.fgcanvas_ctx_ = Dygraph.getContext(this.fgcanvas_); +}; + +/** + * @private + * Creates overlay divs for IE/Excanvas so that mouse events are handled properly. + */ +rangeSelector.prototype.createIEPanOverlay_ = function() { + this.iePanOverlay_ = document.createElement("div"); + this.iePanOverlay_.style.position = 'absolute'; + this.iePanOverlay_.style.backgroundColor = 'white'; + this.iePanOverlay_.style.filter = 'alpha(opacity=0)'; + this.iePanOverlay_.style.display = 'none'; + this.iePanOverlay_.style.cursor = 'move'; + this.fgcanvas_.appendChild(this.iePanOverlay_); +}; + +/** + * @private + * Creates the zoom handle elements. + */ +rangeSelector.prototype.createZoomHandles_ = function() { + var img = new Image(); + img.className = 'dygraph-rangesel-zoomhandle'; + img.style.position = 'absolute'; + img.style.zIndex = 10; + img.style.visibility = 'hidden'; // Initially hidden so they don't show up in the wrong place. + img.style.cursor = 'col-resize'; + + if (/MSIE 7/.test(navigator.userAgent)) { // IE7 doesn't support embedded src data. + img.width = 7; + img.height = 14; + img.style.backgroundColor = 'white'; + img.style.border = '1px solid #333333'; // Just show box in IE7. + } else { + img.width = 9; + img.height = 16; + img.src = 'data:image/png;base64,' + +'iVBORw0KGgoAAAANSUhEUgAAAAkAAAAQCAYAAADESFVDAAAAAXNSR0IArs4c6QAAAAZiS0dEANAA' + +'zwDP4Z7KegAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAAd0SU1FB9sHGw0cMqdt1UwAAAAZdEVYdENv' + +'bW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAAaElEQVQoz+3SsRFAQBCF4Z9WJM8KCDVwownl' + +'6YXsTmCUsyKGkZzcl7zkz3YLkypgAnreFmDEpHkIwVOMfpdi9CEEN2nGpFdwD03yEqDtOgCaun7s' + +'qSTDH32I1pQA2Pb9sZecAxc5r3IAb21d6878xsAAAAAASUVORK5CYII='; + } + + if (this.isMobileDevice_) { + img.width *= 2; + img.height *= 2; + } + + this.leftZoomHandle_ = img; + this.rightZoomHandle_ = img.cloneNode(false); +}; + +/** + * @private + * Sets up the interaction for the range selector. + */ +rangeSelector.prototype.initInteraction_ = function() { + var self = this; + var topElem = this.isIE_ ? document : window; + var clientXLast = 0; + var handle = null; + var isZooming = false; + var isPanning = false; + var dynamic = !this.isMobileDevice_ && !this.isUsingExcanvas_; + + // We cover iframes during mouse interactions. See comments in + // dygraph-utils.js for more info on why this is a good idea. + var tarp = new Dygraph.IFrameTarp(); + + // functions, defined below. Defining them this way (rather than with + // "function foo() {...}" makes JSHint happy. + var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone, + onPanStart, onPan, onPanEnd, doPan, onCanvasHover; + + // Touch event functions + var onZoomHandleTouchEvent, onCanvasTouchEvent, addTouchEvents; + + toXDataWindow = function(zoomHandleStatus) { + var xDataLimits = self.dygraph_.xAxisExtremes(); + var fact = (xDataLimits[1] - xDataLimits[0])/self.canvasRect_.w; + var xDataMin = xDataLimits[0] + (zoomHandleStatus.leftHandlePos - self.canvasRect_.x)*fact; + var xDataMax = xDataLimits[0] + (zoomHandleStatus.rightHandlePos - self.canvasRect_.x)*fact; + return [xDataMin, xDataMax]; + }; + + onZoomStart = function(e) { + Dygraph.cancelEvent(e); + isZooming = true; + clientXLast = e.clientX; + handle = e.target ? e.target : e.srcElement; + if (e.type === 'mousedown' || e.type === 'dragstart') { + // These events are removed manually. + Dygraph.addEvent(topElem, 'mousemove', onZoom); + Dygraph.addEvent(topElem, 'mouseup', onZoomEnd); + } + self.fgcanvas_.style.cursor = 'col-resize'; + tarp.cover(); + return true; + }; + + onZoom = function(e) { + if (!isZooming) { + return false; + } + Dygraph.cancelEvent(e); + + var delX = e.clientX - clientXLast; + if (Math.abs(delX) < 4) { + return true; + } + clientXLast = e.clientX; + + // Move handle. + var zoomHandleStatus = self.getZoomHandleStatus_(); + var newPos; + if (handle == self.leftZoomHandle_) { + newPos = zoomHandleStatus.leftHandlePos + delX; + newPos = Math.min(newPos, zoomHandleStatus.rightHandlePos - handle.width - 3); + newPos = Math.max(newPos, self.canvasRect_.x); + } else { + newPos = zoomHandleStatus.rightHandlePos + delX; + newPos = Math.min(newPos, self.canvasRect_.x + self.canvasRect_.w); + newPos = Math.max(newPos, zoomHandleStatus.leftHandlePos + handle.width + 3); + } + var halfHandleWidth = handle.width/2; + handle.style.left = (newPos - halfHandleWidth) + 'px'; + self.drawInteractiveLayer_(); + + // Zoom on the fly (if not using excanvas). + if (dynamic) { + doZoom(); + } + return true; + }; + + onZoomEnd = function(e) { + if (!isZooming) { + return false; + } + isZooming = false; + tarp.uncover(); + Dygraph.removeEvent(topElem, 'mousemove', onZoom); + Dygraph.removeEvent(topElem, 'mouseup', onZoomEnd); + self.fgcanvas_.style.cursor = 'default'; + + // If using excanvas, Zoom now. + if (!dynamic) { + doZoom(); + } + return true; + }; + + doZoom = function() { + try { + var zoomHandleStatus = self.getZoomHandleStatus_(); + self.isChangingRange_ = true; + if (!zoomHandleStatus.isZoomed) { + self.dygraph_.resetZoom(); + } else { + var xDataWindow = toXDataWindow(zoomHandleStatus); + self.dygraph_.doZoomXDates_(xDataWindow[0], xDataWindow[1]); + } + } finally { + self.isChangingRange_ = false; + } + }; + + isMouseInPanZone = function(e) { + if (self.isUsingExcanvas_) { + return e.srcElement == self.iePanOverlay_; + } else { + var rect = self.leftZoomHandle_.getBoundingClientRect(); + var leftHandleClientX = rect.left + rect.width/2; + rect = self.rightZoomHandle_.getBoundingClientRect(); + var rightHandleClientX = rect.left + rect.width/2; + return (e.clientX > leftHandleClientX && e.clientX < rightHandleClientX); + } + }; + + onPanStart = function(e) { + if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) { + Dygraph.cancelEvent(e); + isPanning = true; + clientXLast = e.clientX; + if (e.type === 'mousedown') { + // These events are removed manually. + Dygraph.addEvent(topElem, 'mousemove', onPan); + Dygraph.addEvent(topElem, 'mouseup', onPanEnd); + } + return true; + } + return false; + }; + + onPan = function(e) { + if (!isPanning) { + return false; + } + Dygraph.cancelEvent(e); + + var delX = e.clientX - clientXLast; + if (Math.abs(delX) < 4) { + return true; + } + clientXLast = e.clientX; + + // Move range view + var zoomHandleStatus = self.getZoomHandleStatus_(); + var leftHandlePos = zoomHandleStatus.leftHandlePos; + var rightHandlePos = zoomHandleStatus.rightHandlePos; + var rangeSize = rightHandlePos - leftHandlePos; + if (leftHandlePos + delX <= self.canvasRect_.x) { + leftHandlePos = self.canvasRect_.x; + rightHandlePos = leftHandlePos + rangeSize; + } else if (rightHandlePos + delX >= self.canvasRect_.x + self.canvasRect_.w) { + rightHandlePos = self.canvasRect_.x + self.canvasRect_.w; + leftHandlePos = rightHandlePos - rangeSize; + } else { + leftHandlePos += delX; + rightHandlePos += delX; + } + var halfHandleWidth = self.leftZoomHandle_.width/2; + self.leftZoomHandle_.style.left = (leftHandlePos - halfHandleWidth) + 'px'; + self.rightZoomHandle_.style.left = (rightHandlePos - halfHandleWidth) + 'px'; + self.drawInteractiveLayer_(); + + // Do pan on the fly (if not using excanvas). + if (dynamic) { + doPan(); + } + return true; + }; + + onPanEnd = function(e) { + if (!isPanning) { + return false; + } + isPanning = false; + Dygraph.removeEvent(topElem, 'mousemove', onPan); + Dygraph.removeEvent(topElem, 'mouseup', onPanEnd); + // If using excanvas, do pan now. + if (!dynamic) { + doPan(); + } + return true; + }; + + doPan = function() { + try { + self.isChangingRange_ = true; + self.dygraph_.dateWindow_ = toXDataWindow(self.getZoomHandleStatus_()); + self.dygraph_.drawGraph_(false); + } finally { + self.isChangingRange_ = false; + } + }; + + onCanvasHover = function(e) { + if (isZooming || isPanning) { + return; + } + var cursor = isMouseInPanZone(e) ? 'move' : 'default'; + if (cursor != self.fgcanvas_.style.cursor) { + self.fgcanvas_.style.cursor = cursor; + } + }; + + onZoomHandleTouchEvent = function(e) { + if (e.type == 'touchstart' && e.targetTouches.length == 1) { + if (onZoomStart(e.targetTouches[0])) { + Dygraph.cancelEvent(e); + } + } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { + if (onZoom(e.targetTouches[0])) { + Dygraph.cancelEvent(e); + } + } else { + onZoomEnd(e); + } + }; + + onCanvasTouchEvent = function(e) { + if (e.type == 'touchstart' && e.targetTouches.length == 1) { + if (onPanStart(e.targetTouches[0])) { + Dygraph.cancelEvent(e); + } + } else if (e.type == 'touchmove' && e.targetTouches.length == 1) { + if (onPan(e.targetTouches[0])) { + Dygraph.cancelEvent(e); + } + } else { + onPanEnd(e); + } + }; + + addTouchEvents = function(elem, fn) { + var types = ['touchstart', 'touchend', 'touchmove', 'touchcancel']; + for (var i = 0; i < types.length; i++) { + self.dygraph_.addAndTrackEvent(elem, types[i], fn); + } + }; + + this.setDefaultOption_('interactionModel', Dygraph.Interaction.dragIsPanInteractionModel); + this.setDefaultOption_('panEdgeFraction', 0.0001); + + var dragStartEvent = window.opera ? 'mousedown' : 'dragstart'; + this.dygraph_.addAndTrackEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart); + this.dygraph_.addAndTrackEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart); + + if (this.isUsingExcanvas_) { + this.dygraph_.addAndTrackEvent(this.iePanOverlay_, 'mousedown', onPanStart); + } else { + this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousedown', onPanStart); + this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousemove', onCanvasHover); + } + + // Touch events + if (this.hasTouchInterface_) { + addTouchEvents(this.leftZoomHandle_, onZoomHandleTouchEvent); + addTouchEvents(this.rightZoomHandle_, onZoomHandleTouchEvent); + addTouchEvents(this.fgcanvas_, onCanvasTouchEvent); + } +}; + +/** + * @private + * Draws the static layer in the background canvas. + */ +rangeSelector.prototype.drawStaticLayer_ = function() { + var ctx = this.bgcanvas_ctx_; + ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); + try { + this.drawMiniPlot_(); + } catch(ex) { + Dygraph.warn(ex); + } + + var margin = 0.5; + this.bgcanvas_ctx_.lineWidth = 1; + ctx.strokeStyle = 'gray'; + ctx.beginPath(); + ctx.moveTo(margin, margin); + ctx.lineTo(margin, this.canvasRect_.h-margin); + ctx.lineTo(this.canvasRect_.w-margin, this.canvasRect_.h-margin); + ctx.lineTo(this.canvasRect_.w-margin, margin); + ctx.stroke(); +}; + + +/** + * @private + * Draws the mini plot in the background canvas. + */ +rangeSelector.prototype.drawMiniPlot_ = function() { + var fillStyle = this.getOption_('rangeSelectorPlotFillColor'); + var strokeStyle = this.getOption_('rangeSelectorPlotStrokeColor'); + if (!fillStyle && !strokeStyle) { + return; + } + + var stepPlot = this.getOption_('stepPlot'); + + var combinedSeriesData = this.computeCombinedSeriesAndLimits_(); + var yRange = combinedSeriesData.yMax - combinedSeriesData.yMin; + + // Draw the mini plot. + var ctx = this.bgcanvas_ctx_; + var margin = 0.5; + + var xExtremes = this.dygraph_.xAxisExtremes(); + var xRange = Math.max(xExtremes[1] - xExtremes[0], 1.e-30); + var xFact = (this.canvasRect_.w - margin)/xRange; + var yFact = (this.canvasRect_.h - margin)/yRange; + var canvasWidth = this.canvasRect_.w - margin; + var canvasHeight = this.canvasRect_.h - margin; + + var prevX = null, prevY = null; + + ctx.beginPath(); + ctx.moveTo(margin, canvasHeight); + for (var i = 0; i < combinedSeriesData.data.length; i++) { + var dataPoint = combinedSeriesData.data[i]; + var x = ((dataPoint[0] !== null) ? ((dataPoint[0] - xExtremes[0])*xFact) : NaN); + var y = ((dataPoint[1] !== null) ? (canvasHeight - (dataPoint[1] - combinedSeriesData.yMin)*yFact) : NaN); + if (isFinite(x) && isFinite(y)) { + if(prevX === null) { + ctx.lineTo(x, canvasHeight); + } + else if (stepPlot) { + ctx.lineTo(x, prevY); + } + ctx.lineTo(x, y); + prevX = x; + prevY = y; + } + else { + if(prevX !== null) { + if (stepPlot) { + ctx.lineTo(x, prevY); + ctx.lineTo(x, canvasHeight); + } + else { + ctx.lineTo(prevX, canvasHeight); + } + } + prevX = prevY = null; + } + } + ctx.lineTo(canvasWidth, canvasHeight); + ctx.closePath(); + + if (fillStyle) { + var lingrad = this.bgcanvas_ctx_.createLinearGradient(0, 0, 0, canvasHeight); + lingrad.addColorStop(0, 'white'); + lingrad.addColorStop(1, fillStyle); + this.bgcanvas_ctx_.fillStyle = lingrad; + ctx.fill(); + } + + if (strokeStyle) { + this.bgcanvas_ctx_.strokeStyle = strokeStyle; + this.bgcanvas_ctx_.lineWidth = 1.5; + ctx.stroke(); + } +}; + +/** + * @private + * Computes and returns the combinded series data along with min/max for the mini plot. + * @return {Object} An object containing combinded series array, ymin, ymax. + */ +rangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() { + var data = this.dygraph_.rawData_; + var logscale = this.getOption_('logscale'); + + // Create a combined series (average of all series values). + var combinedSeries = []; + var sum; + var count; + var mutipleValues; + var i, j, k; + var xVal, yVal; + + // Find out if data has multiple values per datapoint. + // Go to first data point that actually has values (see http://code.google.com/p/dygraphs/issues/detail?id=246) + for (i = 0; i < data.length; i++) { + if (data[i].length > 1 && data[i][1] !== null) { + mutipleValues = typeof data[i][1] != 'number'; + if (mutipleValues) { + sum = []; + count = []; + for (k = 0; k < data[i][1].length; k++) { + sum.push(0); + count.push(0); + } + } + break; + } + } + + for (i = 0; i < data.length; i++) { + var dataPoint = data[i]; + xVal = dataPoint[0]; + + if (mutipleValues) { + for (k = 0; k < sum.length; k++) { + sum[k] = count[k] = 0; + } + } else { + sum = count = 0; + } + + for (j = 1; j < dataPoint.length; j++) { + if (this.dygraph_.visibility()[j-1]) { + var y; + if (mutipleValues) { + for (k = 0; k < sum.length; k++) { + y = dataPoint[j][k]; + if (y === null || isNaN(y)) continue; + sum[k] += y; + count[k]++; + } + } else { + y = dataPoint[j]; + if (y === null || isNaN(y)) continue; + sum += y; + count++; + } + } + } + + if (mutipleValues) { + for (k = 0; k < sum.length; k++) { + sum[k] /= count[k]; + } + yVal = sum.slice(0); + } else { + yVal = sum/count; + } + + combinedSeries.push([xVal, yVal]); + } + + // Account for roll period, fractions. + combinedSeries = this.dygraph_.rollingAverage(combinedSeries, this.dygraph_.rollPeriod_); + + if (typeof combinedSeries[0][1] != 'number') { + for (i = 0; i < combinedSeries.length; i++) { + yVal = combinedSeries[i][1]; + combinedSeries[i][1] = yVal[0]; + } + } + + // Compute the y range. + var yMin = Number.MAX_VALUE; + var yMax = -Number.MAX_VALUE; + for (i = 0; i < combinedSeries.length; i++) { + yVal = combinedSeries[i][1]; + if (yVal !== null && isFinite(yVal) && (!logscale || yVal > 0)) { + yMin = Math.min(yMin, yVal); + yMax = Math.max(yMax, yVal); + } + } + + // Convert Y data to log scale if needed. + // Also, expand the Y range to compress the mini plot a little. + var extraPercent = 0.25; + if (logscale) { + yMax = Dygraph.log10(yMax); + yMax += yMax*extraPercent; + yMin = Dygraph.log10(yMin); + for (i = 0; i < combinedSeries.length; i++) { + combinedSeries[i][1] = Dygraph.log10(combinedSeries[i][1]); + } + } else { + var yExtra; + var yRange = yMax - yMin; + if (yRange <= Number.MIN_VALUE) { + yExtra = yMax*extraPercent; + } else { + yExtra = yRange*extraPercent; + } + yMax += yExtra; + yMin -= yExtra; + } + + return {data: combinedSeries, yMin: yMin, yMax: yMax}; +}; + +/** + * @private + * Places the zoom handles in the proper position based on the current X data window. + */ +rangeSelector.prototype.placeZoomHandles_ = function() { + var xExtremes = this.dygraph_.xAxisExtremes(); + var xWindowLimits = this.dygraph_.xAxisRange(); + var xRange = xExtremes[1] - xExtremes[0]; + var leftPercent = Math.max(0, (xWindowLimits[0] - xExtremes[0])/xRange); + var rightPercent = Math.max(0, (xExtremes[1] - xWindowLimits[1])/xRange); + var leftCoord = this.canvasRect_.x + this.canvasRect_.w*leftPercent; + var rightCoord = this.canvasRect_.x + this.canvasRect_.w*(1 - rightPercent); + var handleTop = Math.max(this.canvasRect_.y, this.canvasRect_.y + (this.canvasRect_.h - this.leftZoomHandle_.height)/2); + var halfHandleWidth = this.leftZoomHandle_.width/2; + this.leftZoomHandle_.style.left = (leftCoord - halfHandleWidth) + 'px'; + this.leftZoomHandle_.style.top = handleTop + 'px'; + this.rightZoomHandle_.style.left = (rightCoord - halfHandleWidth) + 'px'; + this.rightZoomHandle_.style.top = this.leftZoomHandle_.style.top; + + this.leftZoomHandle_.style.visibility = 'visible'; + this.rightZoomHandle_.style.visibility = 'visible'; +}; + +/** + * @private + * Draws the interactive layer in the foreground canvas. + */ +rangeSelector.prototype.drawInteractiveLayer_ = function() { + var ctx = this.fgcanvas_ctx_; + ctx.clearRect(0, 0, this.canvasRect_.w, this.canvasRect_.h); + var margin = 1; + var width = this.canvasRect_.w - margin; + var height = this.canvasRect_.h - margin; + var zoomHandleStatus = this.getZoomHandleStatus_(); + + ctx.strokeStyle = 'black'; + if (!zoomHandleStatus.isZoomed) { + ctx.beginPath(); + ctx.moveTo(margin, margin); + ctx.lineTo(margin, height); + ctx.lineTo(width, height); + ctx.lineTo(width, margin); + ctx.stroke(); + if (this.iePanOverlay_) { + this.iePanOverlay_.style.display = 'none'; + } + } else { + var leftHandleCanvasPos = Math.max(margin, zoomHandleStatus.leftHandlePos - this.canvasRect_.x); + var rightHandleCanvasPos = Math.min(width, zoomHandleStatus.rightHandlePos - this.canvasRect_.x); + + ctx.fillStyle = 'rgba(240, 240, 240, 0.6)'; + ctx.fillRect(0, 0, leftHandleCanvasPos, this.canvasRect_.h); + ctx.fillRect(rightHandleCanvasPos, 0, this.canvasRect_.w - rightHandleCanvasPos, this.canvasRect_.h); + + ctx.beginPath(); + ctx.moveTo(margin, margin); + ctx.lineTo(leftHandleCanvasPos, margin); + ctx.lineTo(leftHandleCanvasPos, height); + ctx.lineTo(rightHandleCanvasPos, height); + ctx.lineTo(rightHandleCanvasPos, margin); + ctx.lineTo(width, margin); + ctx.stroke(); + + if (this.isUsingExcanvas_) { + this.iePanOverlay_.style.width = (rightHandleCanvasPos - leftHandleCanvasPos) + 'px'; + this.iePanOverlay_.style.left = leftHandleCanvasPos + 'px'; + this.iePanOverlay_.style.height = height + 'px'; + this.iePanOverlay_.style.display = 'inline'; + } + } +}; + +/** + * @private + * Returns the current zoom handle position information. + * @return {Object} The zoom handle status. + */ +rangeSelector.prototype.getZoomHandleStatus_ = function() { + var halfHandleWidth = this.leftZoomHandle_.width/2; + var leftHandlePos = parseFloat(this.leftZoomHandle_.style.left) + halfHandleWidth; + var rightHandlePos = parseFloat(this.rightZoomHandle_.style.left) + halfHandleWidth; + return { + leftHandlePos: leftHandlePos, + rightHandlePos: rightHandlePos, + isZoomed: (leftHandlePos - 1 > this.canvasRect_.x || rightHandlePos + 1 < this.canvasRect_.x+this.canvasRect_.w) + }; +}; + +return rangeSelector; + +})(); |