http://git-wip-us.apache.org/repos/asf/flink/blob/3a4fc537/flink-runtime-web/web-dashboard/web/js/vendor.js
----------------------------------------------------------------------
diff --git a/flink-runtime-web/web-dashboard/web/js/vendor.js 
b/flink-runtime-web/web-dashboard/web/js/vendor.js
index 00abf95..a8f96c5 100644
--- a/flink-runtime-web/web-dashboard/web/js/vendor.js
+++ b/flink-runtime-web/web-dashboard/web/js/vendor.js
@@ -48779,7 +48779,7 @@ if (typeof jQuery === 'undefined') {
 
 !function() {
   var d3 = {
-    version: "3.5.12"
+    version: "3.5.16"
   };
   var d3_arraySlice = [].slice, d3_array = function(list) {
     return d3_arraySlice.call(list);
@@ -48999,20 +48999,20 @@ if (typeof jQuery === 'undefined') {
     while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ];
     return pairs;
   };
-  d3.zip = function() {
-    if (!(n = arguments.length)) return [];
-    for (var i = -1, m = d3.min(arguments, d3_zipLength), zips = new Array(m); 
++i < m; ) {
-      for (var j = -1, n, zip = zips[i] = new Array(n); ++j < n; ) {
-        zip[j] = arguments[j][i];
+  d3.transpose = function(matrix) {
+    if (!(n = matrix.length)) return [];
+    for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new 
Array(m); ++i < m; ) {
+      for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) {
+        row[j] = matrix[j][i];
       }
     }
-    return zips;
+    return transpose;
   };
-  function d3_zipLength(d) {
+  function d3_transposeLength(d) {
     return d.length;
   }
-  d3.transpose = function(matrix) {
-    return d3.zip.apply(d3, matrix);
+  d3.zip = function() {
+    return d3.transpose(arguments);
   };
   d3.keys = function(map) {
     var keys = [];
@@ -49399,9 +49399,10 @@ if (typeof jQuery === 'undefined') {
       return d3_selectAll(selector, this);
     };
   }
+  var d3_nsXhtml = "http://www.w3.org/1999/xhtml";;
   var d3_nsPrefix = {
     svg: "http://www.w3.org/2000/svg";,
-    xhtml: "http://www.w3.org/1999/xhtml";,
+    xhtml: d3_nsXhtml,
     xlink: "http://www.w3.org/1999/xlink";,
     xml: "http://www.w3.org/XML/1998/namespace";,
     xmlns: "http://www.w3.org/2000/xmlns/";
@@ -49584,7 +49585,7 @@ if (typeof jQuery === 'undefined') {
   function d3_selection_creator(name) {
     function create() {
       var document = this.ownerDocument, namespace = this.namespaceURI;
-      return namespace ? document.createElementNS(namespace, name) : 
document.createElement(name);
+      return namespace === d3_nsXhtml && document.documentElement.namespaceURI 
=== d3_nsXhtml ? document.createElement(name) : 
document.createElementNS(namespace, name);
     }
     function createNS() {
       return this.ownerDocument.createElementNS(name.space, name.local);
@@ -49983,7 +49984,7 @@ if (typeof jQuery === 'undefined') {
     }
     function dragstart(id, position, subject, move, end) {
       return function() {
-        var that = this, target = d3.event.target, parent = that.parentNode, 
dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = 
".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = 
d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, 
ended), dragRestore = d3_event_dragSuppress(target), position0 = 
position(parent, dragId);
+        var that = this, target = d3.event.target.correspondingElement || 
d3.event.target, parent = that.parentNode, dispatch = event.of(that, 
arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? 
"" : "-" + dragId), dragOffset, dragSubject = 
d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, 
ended), dragRestore = d3_event_dragSuppress(target), position0 = 
position(parent, dragId);
         if (origin) {
           dragOffset = origin.apply(that, arguments);
           dragOffset = [ dragOffset.x - position0[0], dragOffset.y - 
position0[1] ];
@@ -71166,476 +71167,16226 @@ if (typeof jQuery === 'undefined') {
 
 })(this);
 
-/**
- * EvEmitter v1.0.3
- * Lil' event emitter
- * MIT License
+/*!
+ * EventEmitter v4.2.11 - git.io/ee
+ * Unlicense - http://unlicense.org/
+ * Oliver Caldwell - http://oli.me.uk/
+ * @preserve
  */
 
-/* jshint unused: true, undef: true, strict: true */
+;(function () {
+    'use strict';
 
-( function( global, factory ) {
-  // universal module definition
-  /* jshint strict: false */ /* globals define, module, window */
-  if ( typeof define == 'function' && define.amd ) {
-    // AMD - RequireJS
-    define( factory );
-  } else if ( typeof module == 'object' && module.exports ) {
-    // CommonJS - Browserify, Webpack
-    module.exports = factory();
-  } else {
-    // Browser globals
-    global.EvEmitter = factory();
-  }
+    /**
+     * Class for managing events.
+     * Can be extended to provide event functionality in other classes.
+     *
+     * @class EventEmitter Manages event registering and emitting.
+     */
+    function EventEmitter() {}
 
-}( typeof window != 'undefined' ? window : this, function() {
+    // Shortcuts to improve speed and size
+    var proto = EventEmitter.prototype;
+    var exports = this;
+    var originalGlobalValue = exports.EventEmitter;
 
-"use strict";
+    /**
+     * Finds the index of the listener for the event in its storage array.
+     *
+     * @param {Function[]} listeners Array of listeners to search through.
+     * @param {Function} listener Method to look for.
+     * @return {Number} Index of the specified listener, -1 if not found
+     * @api private
+     */
+    function indexOfListener(listeners, listener) {
+        var i = listeners.length;
+        while (i--) {
+            if (listeners[i].listener === listener) {
+                return i;
+            }
+        }
 
-function EvEmitter() {}
+        return -1;
+    }
 
-var proto = EvEmitter.prototype;
+    /**
+     * Alias a method while keeping the context correct, to allow for 
overwriting of target method.
+     *
+     * @param {String} name The name of the target method.
+     * @return {Function} The aliased method
+     * @api private
+     */
+    function alias(name) {
+        return function aliasClosure() {
+            return this[name].apply(this, arguments);
+        };
+    }
 
-proto.on = function( eventName, listener ) {
-  if ( !eventName || !listener ) {
-    return;
-  }
-  // set events hash
-  var events = this._events = this._events || {};
-  // set listeners array
-  var listeners = events[ eventName ] = events[ eventName ] || [];
-  // only add once
-  if ( listeners.indexOf( listener ) == -1 ) {
-    listeners.push( listener );
-  }
+    /**
+     * Returns the listener array for the specified event.
+     * Will initialise the event object and listener arrays if required.
+     * Will return an object if you use a regex search. The object contains 
keys for each matched event. So /ba[rz]/ might return an object containing bar 
and baz. But only if you have either defined them with defineEvent or added 
some listeners to them.
+     * Each property in the object response is an array of listener functions.
+     *
+     * @param {String|RegExp} evt Name of the event to return the listeners 
from.
+     * @return {Function[]|Object} All listener functions for the event.
+     */
+    proto.getListeners = function getListeners(evt) {
+        var events = this._getEvents();
+        var response;
+        var key;
+
+        // Return a concatenated array of all matching events if
+        // the selector is a regular expression.
+        if (evt instanceof RegExp) {
+            response = {};
+            for (key in events) {
+                if (events.hasOwnProperty(key) && evt.test(key)) {
+                    response[key] = events[key];
+                }
+            }
+        }
+        else {
+            response = events[evt] || (events[evt] = []);
+        }
 
-  return this;
-};
+        return response;
+    };
 
-proto.once = function( eventName, listener ) {
-  if ( !eventName || !listener ) {
-    return;
-  }
-  // add event
-  this.on( eventName, listener );
-  // set once flag
-  // set onceEvents hash
-  var onceEvents = this._onceEvents = this._onceEvents || {};
-  // set onceListeners object
-  var onceListeners = onceEvents[ eventName ] = onceEvents[ eventName ] || {};
-  // set flag
-  onceListeners[ listener ] = true;
+    /**
+     * Takes a list of listener objects and flattens it into a list of 
listener functions.
+     *
+     * @param {Object[]} listeners Raw listener objects.
+     * @return {Function[]} Just the listener functions.
+     */
+    proto.flattenListeners = function flattenListeners(listeners) {
+        var flatListeners = [];
+        var i;
 
-  return this;
-};
+        for (i = 0; i < listeners.length; i += 1) {
+            flatListeners.push(listeners[i].listener);
+        }
 
-proto.off = function( eventName, listener ) {
-  var listeners = this._events && this._events[ eventName ];
-  if ( !listeners || !listeners.length ) {
-    return;
-  }
-  var index = listeners.indexOf( listener );
-  if ( index != -1 ) {
-    listeners.splice( index, 1 );
-  }
+        return flatListeners;
+    };
 
-  return this;
-};
+    /**
+     * Fetches the requested listeners via getListeners but will always return 
the results inside an object. This is mainly for internal use but others may 
find it useful.
+     *
+     * @param {String|RegExp} evt Name of the event to return the listeners 
from.
+     * @return {Object} All listener functions for an event in an object.
+     */
+    proto.getListenersAsObject = function getListenersAsObject(evt) {
+        var listeners = this.getListeners(evt);
+        var response;
 
-proto.emitEvent = function( eventName, args ) {
-  var listeners = this._events && this._events[ eventName ];
-  if ( !listeners || !listeners.length ) {
-    return;
-  }
-  var i = 0;
-  var listener = listeners[i];
-  args = args || [];
-  // once stuff
-  var onceListeners = this._onceEvents && this._onceEvents[ eventName ];
+        if (listeners instanceof Array) {
+            response = {};
+            response[evt] = listeners;
+        }
 
-  while ( listener ) {
-    var isOnce = onceListeners && onceListeners[ listener ];
-    if ( isOnce ) {
-      // remove listener
-      // remove before trigger to prevent recursion
-      this.off( eventName, listener );
-      // unset once flag
-      delete onceListeners[ listener ];
-    }
-    // trigger listener
-    listener.apply( this, args );
-    // get next listener
-    i += isOnce ? 0 : 1;
-    listener = listeners[i];
-  }
+        return response || listeners;
+    };
 
-  return this;
-};
+    /**
+     * Adds a listener function to the specified event.
+     * The listener will not be added if it is a duplicate.
+     * If the listener returns true then it will be removed after it is called.
+     * If you pass a regular expression as the event name then the listener 
will be added to all events that match it.
+     *
+     * @param {String|RegExp} evt Name of the event to attach the listener to.
+     * @param {Function} listener Method to be called when the event is 
emitted. If the function returns true then it will be removed after calling.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.addListener = function addListener(evt, listener) {
+        var listeners = this.getListenersAsObject(evt);
+        var listenerIsWrapped = typeof listener === 'object';
+        var key;
+
+        for (key in listeners) {
+            if (listeners.hasOwnProperty(key) && 
indexOfListener(listeners[key], listener) === -1) {
+                listeners[key].push(listenerIsWrapped ? listener : {
+                    listener: listener,
+                    once: false
+                });
+            }
+        }
 
-return EvEmitter;
+        return this;
+    };
 
-}));
+    /**
+     * Alias of addListener
+     */
+    proto.on = alias('addListener');
 
-/* angular-moment.js / v0.10.3 / (c) 2013, 2014, 2015 Uri Shaked / MIT Licence 
*/
+    /**
+     * Semi-alias of addListener. It will add a listener that will be
+     * automatically removed after its first execution.
+     *
+     * @param {String|RegExp} evt Name of the event to attach the listener to.
+     * @param {Function} listener Method to be called when the event is 
emitted. If the function returns true then it will be removed after calling.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.addOnceListener = function addOnceListener(evt, listener) {
+        return this.addListener(evt, {
+            listener: listener,
+            once: true
+        });
+    };
 
-'format amd';
-/* global define */
+    /**
+     * Alias of addOnceListener.
+     */
+    proto.once = alias('addOnceListener');
 
-(function () {
-       'use strict';
+    /**
+     * Defines an event name. This is required if you want to use a regex to 
add a listener to multiple events at once. If you don't do this then how do you 
expect it to know what event to add to? Should it just add to every possible 
match for a regex? No. That is scary and bad.
+     * You need to tell it what event names should be matched by a regex.
+     *
+     * @param {String} evt Name of the event to create.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.defineEvent = function defineEvent(evt) {
+        this.getListeners(evt);
+        return this;
+    };
 
-       function angularMoment(angular, moment) {
+    /**
+     * Uses defineEvent to define multiple events.
+     *
+     * @param {String[]} evts An array of event names to define.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.defineEvents = function defineEvents(evts) {
+        for (var i = 0; i < evts.length; i += 1) {
+            this.defineEvent(evts[i]);
+        }
+        return this;
+    };
 
-               /**
-                * @ngdoc overview
-                * @name angularMoment
-                *
-                * @description
-                * angularMoment module provides moment.js functionality for 
angular.js apps.
-                */
-               return angular.module('angularMoment', [])
+    /**
+     * Removes a listener function from the specified event.
+     * When passed a regular expression as the event name, it will remove the 
listener from all events that match it.
+     *
+     * @param {String|RegExp} evt Name of the event to remove the listener 
from.
+     * @param {Function} listener Method to remove from the event.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.removeListener = function removeListener(evt, listener) {
+        var listeners = this.getListenersAsObject(evt);
+        var index;
+        var key;
 
-               /**
-                * @ngdoc object
-                * @name angularMoment.config:angularMomentConfig
-                *
-                * @description
-                * Common configuration of the angularMoment module
-                */
-                       .constant('angularMomentConfig', {
-                               /**
-                                * @ngdoc property
-                                * @name 
angularMoment.config.angularMomentConfig#preprocess
-                                * @propertyOf 
angularMoment.config:angularMomentConfig
-                                * @returns {string} The default preprocessor 
to apply
-                                *
-                                * @description
-                                * Defines a default preprocessor to apply 
(e.g. 'unix', 'etc', ...). The default value is null,
-                                * i.e. no preprocessor will be applied.
-                                */
-                               preprocess: null, // e.g. 'unix', 'utc', ...
+        for (key in listeners) {
+            if (listeners.hasOwnProperty(key)) {
+                index = indexOfListener(listeners[key], listener);
 
-                               /**
-                                * @ngdoc property
-                                * @name 
angularMoment.config.angularMomentConfig#timezone
-                                * @propertyOf 
angularMoment.config:angularMomentConfig
-                                * @returns {string} The default timezone
-                                *
-                                * @description
-                                * The default timezone (e.g. 'Europe/London'). 
Empty string by default (does not apply
-                                * any timezone shift).
-                                */
-                               timezone: '',
+                if (index !== -1) {
+                    listeners[key].splice(index, 1);
+                }
+            }
+        }
 
-                               /**
-                                * @ngdoc property
-                                * @name 
angularMoment.config.angularMomentConfig#format
-                                * @propertyOf 
angularMoment.config:angularMomentConfig
-                                * @returns {string} The pre-conversion format 
of the date
-                                *
-                                * @description
-                                * Specify the format of the input date. 
Essentially it's a
-                                * default and saves you from specifying a 
format in every
-                                * element. Overridden by element attr. Null by 
default.
-                                */
-                               format: null,
+        return this;
+    };
 
-                               /**
-                                * @ngdoc property
-                                * @name 
angularMoment.config.angularMomentConfig#statefulFilters
-                                * @propertyOf 
angularMoment.config:angularMomentConfig
-                                * @returns {boolean} Whether angular-moment 
filters should be stateless (or not)
-                                *
-                                * @description
-                                * Specifies whether the filters included with 
angular-moment are stateful.
-                                * Stateful filters will automatically 
re-evaluate whenever you change the timezone
-                                * or language settings, but may negatively 
impact performance. true by default.
-                                */
-                               statefulFilters: true
-                       })
+    /**
+     * Alias of removeListener
+     */
+    proto.off = alias('removeListener');
 
-               /**
-                * @ngdoc object
-                * @name angularMoment.object:moment
-                *
-                * @description
-                * moment global (as provided by the moment.js library)
-                */
-                       .constant('moment', moment)
+    /**
+     * Adds listeners in bulk using the manipulateListeners method.
+     * If you pass an object as the second argument you can add to multiple 
events at once. The object should contain key value pairs of events and 
listeners or listener arrays. You can also pass it an event name and an array 
of listeners to be added.
+     * You can also pass it a regular expression to add the array of listeners 
to all events that match it.
+     * Yeah, this function does quite a bit. That's probably a bad thing.
+     *
+     * @param {String|Object|RegExp} evt An event name if you will pass an 
array of listeners next. An object if you wish to add to multiple events at 
once.
+     * @param {Function[]} [listeners] An optional array of listener functions 
to add.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.addListeners = function addListeners(evt, listeners) {
+        // Pass through to manipulateListeners
+        return this.manipulateListeners(false, evt, listeners);
+    };
 
-               /**
-                * @ngdoc object
-                * @name angularMoment.config:amTimeAgoConfig
-                * @module angularMoment
-                *
-                * @description
-                * configuration specific to the amTimeAgo directive
-                */
-                       .constant('amTimeAgoConfig', {
-                               /**
-                                * @ngdoc property
-                                * @name 
angularMoment.config.amTimeAgoConfig#withoutSuffix
-                                * @propertyOf 
angularMoment.config:amTimeAgoConfig
-                                * @returns {boolean} Whether to include a 
suffix in am-time-ago directive
-                                *
-                                * @description
-                                * Defaults to false.
-                                */
-                               withoutSuffix: false,
+    /**
+     * Removes listeners in bulk using the manipulateListeners method.
+     * If you pass an object as the second argument you can remove from 
multiple events at once. The object should contain key value pairs of events 
and listeners or listener arrays.
+     * You can also pass it an event name and an array of listeners to be 
removed.
+     * You can also pass it a regular expression to remove the listeners from 
all events that match it.
+     *
+     * @param {String|Object|RegExp} evt An event name if you will pass an 
array of listeners next. An object if you wish to remove from multiple events 
at once.
+     * @param {Function[]} [listeners] An optional array of listener functions 
to remove.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.removeListeners = function removeListeners(evt, listeners) {
+        // Pass through to manipulateListeners
+        return this.manipulateListeners(true, evt, listeners);
+    };
 
-                               /**
-                                * @ngdoc property
-                                * @name 
angularMoment.config.amTimeAgoConfig#serverTime
-                                * @propertyOf 
angularMoment.config:amTimeAgoConfig
-                                * @returns {number} Server time in 
milliseconds since the epoch
-                                *
-                                * @description
-                                * If set, time ago will be calculated relative 
to the given value.
-                                * If null, local time will be used. Defaults 
to null.
-                                */
-                               serverTime: null,
+    /**
+     * Edits listeners in bulk. The addListeners and removeListeners methods 
both use this to do their job. You should really use those instead, this is a 
little lower level.
+     * The first argument will determine if the listeners are removed (true) 
or added (false).
+     * If you pass an object as the second argument you can add/remove from 
multiple events at once. The object should contain key value pairs of events 
and listeners or listener arrays.
+     * You can also pass it an event name and an array of listeners to be 
added/removed.
+     * You can also pass it a regular expression to manipulate the listeners 
of all events that match it.
+     *
+     * @param {Boolean} remove True if you want to remove listeners, false if 
you want to add.
+     * @param {String|Object|RegExp} evt An event name if you will pass an 
array of listeners next. An object if you wish to add/remove from multiple 
events at once.
+     * @param {Function[]} [listeners] An optional array of listener functions 
to add/remove.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.manipulateListeners = function manipulateListeners(remove, evt, 
listeners) {
+        var i;
+        var value;
+        var single = remove ? this.removeListener : this.addListener;
+        var multiple = remove ? this.removeListeners : this.addListeners;
+
+        // If evt is an object then pass each of its properties to this method
+        if (typeof evt === 'object' && !(evt instanceof RegExp)) {
+            for (i in evt) {
+                if (evt.hasOwnProperty(i) && (value = evt[i])) {
+                    // Pass the single listener straight through to the 
singular method
+                    if (typeof value === 'function') {
+                        single.call(this, i, value);
+                    }
+                    else {
+                        // Otherwise pass back to the multiple function
+                        multiple.call(this, i, value);
+                    }
+                }
+            }
+        }
+        else {
+            // So evt must be a string
+            // And listeners must be an array of listeners
+            // Loop over it and pass each one to the multiple method
+            i = listeners.length;
+            while (i--) {
+                single.call(this, evt, listeners[i]);
+            }
+        }
 
-                               /**
-                                * @ngdoc property
-                                * @name 
angularMoment.config.amTimeAgoConfig#titleFormat
-                                * @propertyOf 
angularMoment.config:amTimeAgoConfig
-                                * @returns {string} The format of the date to 
be displayed in the title of the element. If null,
-                                *        the directive set the title of the 
element.
-                                *
-                                * @description
-                                * The format of the date used for the title of 
the element. null by default.
-                                */
-                               titleFormat: null,
+        return this;
+    };
 
-                               /**
-                                * @ngdoc property
-                                * @name 
angularMoment.config.amTimeAgoConfig#fullDateThreshold
-                                * @propertyOf 
angularMoment.config:amTimeAgoConfig
-                                * @returns {number} The minimum number of days 
for showing a full date instead of relative time
-                                *
-                                * @description
-                                * The threshold for displaying a full date. 
The default is null, which means the date will always
-                                * be relative, and full date will never be 
displayed.
-                                */
-                               fullDateThreshold: null,
+    /**
+     * Removes all listeners from a specified event.
+     * If you do not specify an event then all listeners will be removed.
+     * That means every event will be emptied.
+     * You can also pass a regex to remove all events that match it.
+     *
+     * @param {String|RegExp} [evt] Optional name of the event to remove all 
listeners for. Will remove from every event if not passed.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.removeEvent = function removeEvent(evt) {
+        var type = typeof evt;
+        var events = this._getEvents();
+        var key;
+
+        // Remove different things depending on the state of evt
+        if (type === 'string') {
+            // Remove all listeners for the specified event
+            delete events[evt];
+        }
+        else if (evt instanceof RegExp) {
+            // Remove all events matching the regex.
+            for (key in events) {
+                if (events.hasOwnProperty(key) && evt.test(key)) {
+                    delete events[key];
+                }
+            }
+        }
+        else {
+            // Remove all listeners in all events
+            delete this._events;
+        }
 
-                               /**
-                                * @ngdoc property
-                                * @name 
angularMoment.config.amTimeAgoConfig#fullDateFormat
-                                * @propertyOf 
angularMoment.config:amTimeAgoConfig
-                                * @returns {string} The format to use when 
displaying a full date.
-                                *
-                                * @description
-                                * Specify the format of the date when 
displayed as full date. null by default.
-                                */
-                               fullDateFormat: null
-                       })
+        return this;
+    };
 
-               /**
-                * @ngdoc directive
-                * @name angularMoment.directive:amTimeAgo
-                * @module angularMoment
-                *
-                * @restrict A
-                */
-                       .directive('amTimeAgo', ['$window', 'moment', 
'amMoment', 'amTimeAgoConfig', 'angularMomentConfig', function ($window, 
moment, amMoment, amTimeAgoConfig, angularMomentConfig) {
+    /**
+     * Alias of removeEvent.
+     *
+     * Added to mirror the node API.
+     */
+    proto.removeAllListeners = alias('removeEvent');
 
-                               return function (scope, element, attr) {
-                                       var activeTimeout = null;
-                                       var currentValue;
-                                       var currentFormat = 
angularMomentConfig.format;
-                                       var withoutSuffix = 
amTimeAgoConfig.withoutSuffix;
-                                       var titleFormat = 
amTimeAgoConfig.titleFormat;
-                                       var fullDateThreshold = 
amTimeAgoConfig.fullDateThreshold;
-                                       var fullDateFormat = 
amTimeAgoConfig.fullDateFormat;
-                                       var localDate = new Date().getTime();
-                                       var preprocess = 
angularMomentConfig.preprocess;
-                                       var modelName = attr.amTimeAgo;
-                                       var currentFrom;
-                                       var isTimeElement = ('TIME' === 
element[0].nodeName.toUpperCase());
+    /**
+     * Emits an event of your choice.
+     * When emitted, every listener attached to that event will be executed.
+     * If you pass the optional argument array then those arguments will be 
passed to every listener upon execution.
+     * Because it uses `apply`, your array of arguments will be passed as if 
you wrote them out separately.
+     * So they will not arrive within the array on the other side, they will 
be separate.
+     * You can also pass a regular expression to emit to all events that match 
it.
+     *
+     * @param {String|RegExp} evt Name of the event to emit and execute 
listeners for.
+     * @param {Array} [args] Optional array of arguments to be passed to each 
listener.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.emitEvent = function emitEvent(evt, args) {
+        var listenersMap = this.getListenersAsObject(evt);
+        var listeners;
+        var listener;
+        var i;
+        var key;
+        var response;
 
-                                       function getNow() {
-                                               var now;
-                                               if (currentFrom) {
-                                                       now = currentFrom;
-                                               } else if 
(amTimeAgoConfig.serverTime) {
-                                                       var localNow = new 
Date().getTime();
-                                                       var nowMillis = 
localNow - localDate + amTimeAgoConfig.serverTime;
-                                                       now = moment(nowMillis);
-                                               }
-                                               else {
-                                                       now = moment();
-                                               }
-                                               return now;
-                                       }
+        for (key in listenersMap) {
+            if (listenersMap.hasOwnProperty(key)) {
+                listeners = listenersMap[key].slice(0);
+                i = listeners.length;
 
-                                       function cancelTimer() {
-                                               if (activeTimeout) {
-                                                       
$window.clearTimeout(activeTimeout);
-                                                       activeTimeout = null;
-                                               }
-                                       }
+                while (i--) {
+                    // If the listener returns true then it shall be removed 
from the event
+                    // The function is executed either with a basic call or an 
apply if there is an args array
+                    listener = listeners[i];
 
-                                       function updateTime(momentInstance) {
-                                               var daysAgo = 
getNow().diff(momentInstance, 'day');
-                                               var showFullDate = 
fullDateThreshold && daysAgo >= fullDateThreshold;
+                    if (listener.once === true) {
+                        this.removeListener(evt, listener.listener);
+                    }
 
-                                               if (showFullDate) {
-                                                       
element.text(momentInstance.format(fullDateFormat));
-                                               } else {
-                                                       
element.text(momentInstance.from(getNow(), withoutSuffix));
-                                               }
+                    response = listener.listener.apply(this, args || []);
 
-                                               if (titleFormat && 
!element.attr('title')) {
-                                                       element.attr('title', 
momentInstance.local().format(titleFormat));
-                                               }
+                    if (response === this._getOnceReturnValue()) {
+                        this.removeListener(evt, listener.listener);
+                    }
+                }
+            }
+        }
 
-                                               if (!showFullDate) {
-                                                       var howOld = 
Math.abs(getNow().diff(momentInstance, 'minute'));
-                                                       var secondsUntilUpdate 
= 3600;
-                                                       if (howOld < 1) {
-                                                               
secondsUntilUpdate = 1;
-                                                       } else if (howOld < 60) 
{
-                                                               
secondsUntilUpdate = 30;
-                                                       } else if (howOld < 
180) {
-                                                               
secondsUntilUpdate = 300;
-                                                       }
+        return this;
+    };
 
-                                                       activeTimeout = 
$window.setTimeout(function () {
-                                                               
updateTime(momentInstance);
-                                                       }, secondsUntilUpdate * 
1000);
-                                               }
-                                       }
+    /**
+     * Alias of emitEvent
+     */
+    proto.trigger = alias('emitEvent');
 
-                                       function updateDateTimeAttr(value) {
-                                               if (isTimeElement) {
-                                                       
element.attr('datetime', value);
-                                               }
-                                       }
+    /**
+     * Subtly different from emitEvent in that it will pass its arguments on 
to the listeners, as opposed to taking a single array of arguments to pass on.
+     * As with emitEvent, you can pass a regex in place of the event name to 
emit to all events that match it.
+     *
+     * @param {String|RegExp} evt Name of the event to emit and execute 
listeners for.
+     * @param {...*} Optional additional arguments to be passed to each 
listener.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.emit = function emit(evt) {
+        var args = Array.prototype.slice.call(arguments, 1);
+        return this.emitEvent(evt, args);
+    };
 
-                                       function updateMoment() {
-                                               cancelTimer();
-                                               if (currentValue) {
-                                                       var momentValue = 
amMoment.preprocessDate(currentValue, preprocess, currentFormat);
-                                                       updateTime(momentValue);
-                                                       
updateDateTimeAttr(momentValue.toISOString());
-                                               }
-                                       }
+    /**
+     * Sets the current value to check against when executing listeners. If a
+     * listeners return value matches the one set here then it will be removed
+     * after execution. This value defaults to true.
+     *
+     * @param {*} value The new value to check for when executing listeners.
+     * @return {Object} Current instance of EventEmitter for chaining.
+     */
+    proto.setOnceReturnValue = function setOnceReturnValue(value) {
+        this._onceReturnValue = value;
+        return this;
+    };
 
-                                       scope.$watch(modelName, function 
(value) {
-                                               if ((typeof value === 
'undefined') || (value === null) || (value === '')) {
-                                                       cancelTimer();
-                                                       if (currentValue) {
-                                                               
element.text('');
-                                                               
updateDateTimeAttr('');
-                                                               currentValue = 
null;
-                                                       }
-                                                       return;
-                                               }
+    /**
+     * Fetches the current value to check against when executing listeners. If
+     * the listeners return value matches this one then it should be removed
+     * automatically. It will return true by default.
+     *
+     * @return {*|Boolean} The current value to check for or the default, true.
+     * @api private
+     */
+    proto._getOnceReturnValue = function _getOnceReturnValue() {
+        if (this.hasOwnProperty('_onceReturnValue')) {
+            return this._onceReturnValue;
+        }
+        else {
+            return true;
+        }
+    };
 
-                                               currentValue = value;
-                                               updateMoment();
-                                       });
+    /**
+     * Fetches the events object and creates one if required.
+     *
+     * @return {Object} The events storage object.
+     * @api private
+     */
+    proto._getEvents = function _getEvents() {
+        return this._events || (this._events = {});
+    };
 
-                                       if (angular.isDefined(attr.amFrom)) {
-                                               scope.$watch(attr.amFrom, 
function (value) {
-                                                       if ((typeof value === 
'undefined') || (value === null) || (value === '')) {
-                                                               currentFrom = 
null;
-                                                       } else {
-                                                               currentFrom = 
moment(value);
-                                                       }
-                                                       updateMoment();
-                                               });
-                                       }
+    /**
+     * Reverts the global {@link EventEmitter} to its previous value and 
returns a reference to this version.
+     *
+     * @return {Function} Non conflicting EventEmitter class.
+     */
+    EventEmitter.noConflict = function noConflict() {
+        exports.EventEmitter = originalGlobalValue;
+        return EventEmitter;
+    };
 
-                                       if 
(angular.isDefined(attr.amWithoutSuffix)) {
-                                               
scope.$watch(attr.amWithoutSuffix, function (value) {
-                                                       if (typeof value === 
'boolean') {
-                                                               withoutSuffix = 
value;
-                                                               updateMoment();
-                                                       } else {
-                                                               withoutSuffix = 
amTimeAgoConfig.withoutSuffix;
-                                                       }
-                                               });
-                                       }
+    // Expose the class either via AMD, CommonJS or the global object
+    if (typeof define === 'function' && define.amd) {
+        define(function () {
+            return EventEmitter;
+        });
+    }
+    else if (typeof module === 'object' && module.exports){
+        module.exports = EventEmitter;
+    }
+    else {
+        exports.EventEmitter = EventEmitter;
+    }
+}.call(this));
 
-                                       attr.$observe('amFormat', function 
(format) {
-                                               if (typeof format !== 
'undefined') {
-                                                       currentFormat = format;
-                                                       updateMoment();
-                                               }
-                                       });
+/*!
+ * eventie v1.0.6
+ * event binding helper
+ *   eventie.bind( elem, 'click', myFn )
+ *   eventie.unbind( elem, 'click', myFn )
+ * MIT license
+ */
 
-                                       attr.$observe('amPreprocess', function 
(newValue) {
-                                               preprocess = newValue;
-                                               updateMoment();
-                                       });
+/*jshint browser: true, undef: true, unused: true */
+/*global define: false, module: false */
 
-                                       attr.$observe('amFullDateThreshold', 
function (newValue) {
-                                               fullDateThreshold = newValue;
-                                               updateMoment();
-                                       });
+( function( window ) {
 
-                                       attr.$observe('amFullDateFormat', 
function (newValue) {
-                                               fullDateFormat = newValue;
-                                               updateMoment();
-                                       });
+'use strict';
 
-                                       scope.$on('$destroy', function () {
-                                               cancelTimer();
-                                       });
+var docElem = document.documentElement;
 
-                                       scope.$on('amMoment:localeChanged', 
function () {
-                                               updateMoment();
-                                       });
-                               };
-                       }])
+var bind = function() {};
 
-               /**
-                * @ngdoc service
-                * @name angularMoment.service.amMoment
-                * @module angularMoment
-                */
-                       .service('amMoment', ['moment', '$rootScope', '$log', 
'angularMomentConfig', function (moment, $rootScope, $log, angularMomentConfig) 
{
-                               /**
-                                * @ngdoc property
-                                * @name angularMoment:amMoment#preprocessors
-                                * @module angularMoment
-                                *
-                                * @description
-                                * Defines the preprocessors for the 
preprocessDate method. By default, the following preprocessors
-                                * are defined: utc, unix.
-                                */
-                               this.preprocessors = {
-                                       utc: moment.utc,
-                                       unix: moment.unix
-                               };
+function getIEEvent( obj ) {
+  var event = window.event;
+  // add event.target
+  event.target = event.target || event.srcElement || obj;
+  return event;
+}
 
-                               /**
-                                * @ngdoc function
-                                * @name 
angularMoment.service.amMoment#changeLocale
-                                * @methodOf angularMoment.service.amMoment
-                                *
-                                * @description
-                                * Changes the locale for moment.js and updates 
all the am-time-ago directive instances
-                                * with the new locale. Also broadcasts an 
`amMoment:localeChanged` event on $rootScope.
-                                *
-                                * @param {string} locale Locale code (e.g. en, 
es, ru, pt-br, etc.)
-                                * @param {object} customization object of 
locale strings to override
-                                */
-                               this.changeLocale = function (locale, 
customization) {
-                                       var result = moment.locale(locale, 
customization);
-                                       if (angular.isDefined(locale)) {
-                                               
$rootScope.$broadcast('amMoment:localeChanged');
+if ( docElem.addEventListener ) {
+  bind = function( obj, type, fn ) {
+    obj.addEventListener( type, fn, false );
+  };
+} else if ( docElem.attachEvent ) {
+  bind = function( obj, type, fn ) {
+    obj[ type + fn ] = fn.handleEvent ?
+      function() {
+        var event = getIEEvent( obj );
+        fn.handleEvent.call( fn, event );
+      } :
+      function() {
+        var event = getIEEvent( obj );
+        fn.call( obj, event );
+      };
+    obj.attachEvent( "on" + type, obj[ type + fn ] );
+  };
+}
 
-                                       }
-                                       return result;
-                               };
+var unbind = function() {};
 
-                               /**
-                                * @ngdoc function
-                                * @name 
angularMoment.service.amMoment#changeTimezone
-                                * @methodOf angularMoment.service.amMoment
+if ( docElem.removeEventListener ) {
+  unbind = function( obj, type, fn ) {
+    obj.removeEventListener( type, fn, false );
+  };
+} else if ( docElem.detachEvent ) {
+  unbind = function( obj, type, fn ) {
+    obj.detachEvent( "on" + type, obj[ type + fn ] );
+    try {
+      delete obj[ type + fn ];
+    } catch ( err ) {
+      // can't delete window object properties
+      obj[ type + fn ] = undefined;
+    }
+  };
+}
+
+var eventie = {
+  bind: bind,
+  unbind: unbind
+};
+
+// ----- module definition ----- //
+
+if ( typeof define === 'function' && define.amd ) {
+  // AMD
+  define( eventie );
+} else if ( typeof exports === 'object' ) {
+  // CommonJS
+  module.exports = eventie;
+} else {
+  // browser global
+  window.eventie = eventie;
+}
+
+})( window );
+
+/**
+ * angular-drag-and-drop-lists v1.4.0
+ *
+ * Copyright (c) 2014 Marcel Juenemann [email protected]
+ * Copyright (c) 2014-2016 Google Inc.
+ * https://github.com/marceljuenemann/angular-drag-and-drop-lists
+ *
+ * License: MIT
+ */
+angular.module('dndLists', [])
+
+  /**
+   * Use the dnd-draggable attribute to make your element draggable
+   *
+   * Attributes:
+   * - dnd-draggable      Required attribute. The value has to be an object 
that represents the data
+   *                      of the element. In case of a drag and drop operation 
the object will be
+   *                      serialized and unserialized on the receiving end.
+   * - dnd-selected       Callback that is invoked when the element was 
clicked but not dragged.
+   *                      The original click event will be provided in the 
local event variable.
+   * - dnd-effect-allowed Use this attribute to limit the operations that can 
be performed. Options:
+   *                      - "move": The drag operation will move the element. 
This is the default.
+   *                      - "copy": The drag operation will copy the element. 
Shows a copy cursor.
+   *                      - "copyMove": The user can choose between copy and 
move by pressing the
+   *                        ctrl or shift key. *Not supported in IE:* In 
Internet Explorer this
+   *                        option will be the same as "copy". *Not fully 
supported in Chrome on
+   *                        Windows:* In the Windows version of Chrome the 
cursor will always be the
+   *                        move cursor. However, when the user drops an 
element and has the ctrl
+   *                        key pressed, we will perform a copy anyways.
+   *                      - HTML5 also specifies the "link" option, but this 
library does not
+   *                        actively support it yet, so use it at your own 
risk.
+   * - dnd-moved          Callback that is invoked when the element was moved. 
Usually you will
+   *                      remove your element from the original list in this 
callback, since the
+   *                      directive is not doing that for you automatically. 
The original dragend
+   *                      event will be provided in the local event variable.
+   * - dnd-canceled       Callback that is invoked if the element was dragged, 
but the operation was
+   *                      canceled and the element was not dropped. The 
original dragend event will
+   *                      be provided in the local event variable.
+   * - dnd-copied         Same as dnd-moved, just that it is called when the 
element was copied
+   *                      instead of moved. The original dragend event will be 
provided in the local
+   *                      event variable.
+   * - dnd-dragstart      Callback that is invoked when the element was 
dragged. The original
+   *                      dragstart event will be provided in the local event 
variable.
+   * - dnd-dragend        Callback that is invoked when the drag operation 
ended. Available local
+   *                      variables are event and dropEffect.
+   * - dnd-type           Use this attribute if you have different kinds of 
items in your
+   *                      application and you want to limit which items can be 
dropped into which
+   *                      lists. Combine with dnd-allowed-types on the 
dnd-list(s). This attribute
+   *                      should evaluate to a string, although this 
restriction is not enforced.
+   * - dnd-disable-if     You can use this attribute to dynamically disable 
the draggability of the
+   *                      element. This is useful if you have certain list 
items that you don't want
+   *                      to be draggable, or if you want to disable drag & 
drop completely without
+   *                      having two different code branches (e.g. only allow 
for admins).
+   *                      **Note**: If your element is not draggable, the user 
is probably able to
+   *                      select text or images inside of it. Since a 
selection is always draggable,
+   *                      this breaks your UI. You most likely want to disable 
user selection via
+   *                      CSS (see user-select).
+   *
+   * CSS classes:
+   * - dndDragging        This class will be added to the element while the 
element is being
+   *                      dragged. It will affect both the element you see 
while dragging and the
+   *                      source element that stays at it's position. Do not 
try to hide the source
+   *                      element with this class, because that will abort the 
drag operation.
+   * - dndDraggingSource  This class will be added to the element after the 
drag operation was
+   *                      started, meaning it only affects the original 
element that is still at
+   *                      it's source position, and not the "element" that the 
user is dragging with
+   *                      his mouse pointer.
+   */
+  .directive('dndDraggable', ['$parse', '$timeout', 'dndDropEffectWorkaround', 
'dndDragTypeWorkaround',
+                      function($parse,   $timeout,   dndDropEffectWorkaround,  
 dndDragTypeWorkaround) {
+    return function(scope, element, attr) {
+      // Set the HTML5 draggable attribute on the element
+      element.attr("draggable", "true");
+
+      // If the dnd-disable-if attribute is set, we have to watch that
+      if (attr.dndDisableIf) {
+        scope.$watch(attr.dndDisableIf, function(disabled) {
+          element.attr("draggable", !disabled);
+        });
+      }
+
+      /**
+       * When the drag operation is started we have to prepare the 
dataTransfer object,
+       * which is the primary way we communicate with the target element
+       */
+      element.on('dragstart', function(event) {
+        event = event.originalEvent || event;
+
+        // Check whether the element is draggable, since dragstart might be 
triggered on a child.
+        if (element.attr('draggable') == 'false') return true;
+
+        // Serialize the data associated with this element. IE only supports 
the Text drag type
+        event.dataTransfer.setData("Text", 
angular.toJson(scope.$eval(attr.dndDraggable)));
+
+        // Only allow actions specified in dnd-effect-allowed attribute
+        event.dataTransfer.effectAllowed = attr.dndEffectAllowed || "move";
+
+        // Add CSS classes. See documentation above
+        element.addClass("dndDragging");
+        $timeout(function() { element.addClass("dndDraggingSource"); }, 0);
+
+        // Workarounds for stupid browsers, see description below
+        dndDropEffectWorkaround.dropEffect = "none";
+        dndDragTypeWorkaround.isDragging = true;
+
+        // Save type of item in global state. Usually, this would go into the 
dataTransfer
+        // typename, but we have to use "Text" there to support IE
+        dndDragTypeWorkaround.dragType = attr.dndType ? 
scope.$eval(attr.dndType) : undefined;
+
+        // Try setting a proper drag image if triggered on a dnd-handle (won't 
work in IE).
+        if (event._dndHandle && event.dataTransfer.setDragImage) {
+          event.dataTransfer.setDragImage(element[0], 0, 0);
+        }
+
+        // Invoke callback
+        $parse(attr.dndDragstart)(scope, {event: event});
+
+        event.stopPropagation();
+      });
+
+      /**
+       * The dragend event is triggered when the element was dropped or when 
the drag
+       * operation was aborted (e.g. hit escape button). Depending on the 
executed action
+       * we will invoke the callbacks specified with the dnd-moved or 
dnd-copied attribute.
+       */
+      element.on('dragend', function(event) {
+        event = event.originalEvent || event;
+
+        // Invoke callbacks. Usually we would use 
event.dataTransfer.dropEffect to determine
+        // the used effect, but Chrome has not implemented that field 
correctly. On Windows
+        // it always sets it to 'none', while Chrome on Linux sometimes sets 
it to something
+        // else when it's supposed to send 'none' (drag operation aborted).
+        var dropEffect = dndDropEffectWorkaround.dropEffect;
+        scope.$apply(function() {
+          switch (dropEffect) {
+            case "move":
+              $parse(attr.dndMoved)(scope, {event: event});
+              break;
+            case "copy":
+              $parse(attr.dndCopied)(scope, {event: event});
+              break;
+            case "none":
+              $parse(attr.dndCanceled)(scope, {event: event});
+              break;
+          }
+          $parse(attr.dndDragend)(scope, {event: event, dropEffect: 
dropEffect});
+        });
+
+        // Clean up
+        element.removeClass("dndDragging");
+        $timeout(function() { element.removeClass("dndDraggingSource"); }, 0);
+        dndDragTypeWorkaround.isDragging = false;
+        event.stopPropagation();
+      });
+
+      /**
+       * When the element is clicked we invoke the callback function
+       * specified with the dnd-selected attribute.
+       */
+      element.on('click', function(event) {
+        if (!attr.dndSelected) return;
+
+        event = event.originalEvent || event;
+        scope.$apply(function() {
+          $parse(attr.dndSelected)(scope, {event: event});
+        });
+
+        // Prevent triggering dndSelected in parent elements.
+        event.stopPropagation();
+      });
+
+      /**
+       * Workaround to make element draggable in IE9
+       */
+      element.on('selectstart', function() {
+        if (this.dragDrop) this.dragDrop();
+      });
+    };
+  }])
+
+  /**
+   * Use the dnd-list attribute to make your list element a dropzone. Usually 
you will add a single
+   * li element as child with the ng-repeat directive. If you don't do that, 
we will not be able to
+   * position the dropped element correctly. If you want your list to be 
sortable, also add the
+   * dnd-draggable directive to your li element(s). Both the dnd-list and it's 
direct children must
+   * have position: relative CSS style, otherwise the positioning algorithm 
will not be able to
+   * determine the correct placeholder position in all browsers.
+   *
+   * Attributes:
+   * - dnd-list             Required attribute. The value has to be the array 
in which the data of
+   *                        the dropped element should be inserted.
+   * - dnd-allowed-types    Optional array of allowed item types. When used, 
only items that had a
+   *                        matching dnd-type attribute will be dropable.
+   * - dnd-disable-if       Optional boolean expresssion. When it evaluates to 
true, no dropping
+   *                        into the list is possible. Note that this also 
disables rearranging
+   *                        items inside the list.
+   * - dnd-horizontal-list  Optional boolean expresssion. When it evaluates to 
true, the positioning
+   *                        algorithm will use the left and right halfs of the 
list items instead of
+   *                        the upper and lower halfs.
+   * - dnd-dragover         Optional expression that is invoked when an 
element is dragged over the
+   *                        list. If the expression is set, but does not 
return true, the element is
+   *                        not allowed to be dropped. The following variables 
will be available:
+   *                        - event: The original dragover event sent by the 
browser.
+   *                        - index: The position in the list at which the 
element would be dropped.
+   *                        - type: The dnd-type set on the dnd-draggable, or 
undefined if unset.
+   *                        - external: Whether the element was dragged from 
an external source.
+   * - dnd-drop             Optional expression that is invoked when an 
element is dropped on the
+   *                        list. The following variables will be available:
+   *                        - event: The original drop event sent by the 
browser.
+   *                        - index: The position in the list at which the 
element would be dropped.
+   *                        - item: The transferred object.
+   *                        - type: The dnd-type set on the dnd-draggable, or 
undefined if unset.
+   *                        - external: Whether the element was dragged from 
an external source.
+   *                        The return value determines the further handling 
of the drop:
+   *                        - false: The drop will be canceled and the element 
won't be inserted.
+   *                        - true: Signalises that the drop is allowed, but 
the dnd-drop
+   *                          callback already took care of inserting the 
element.
+   *                        - otherwise: All other return values will be 
treated as the object to
+   *                          insert into the array. In most cases you want to 
simply return the
+   *                          item parameter, but there are no restrictions on 
what you can return.
+   * - dnd-inserted         Optional expression that is invoked after a drop 
if the element was
+   *                        actually inserted into the list. The same local 
variables as for
+   *                        dnd-drop will be available. Note that for 
reorderings inside the same
+   *                        list the old element will still be in the list due 
to the fact that
+   *                        dnd-moved was not called yet.
+   * - dnd-external-sources Optional boolean expression. When it evaluates to 
true, the list accepts
+   *                        drops from sources outside of the current browser 
tab. This allows to
+   *                        drag and drop accross different browser tabs. Note 
that this will allow
+   *                        to drop arbitrary text into the list, thus it is 
highly recommended to
+   *                        implement the dnd-drop callback to check the 
incoming element for
+   *                        sanity. Furthermore, the dnd-type of external 
sources can not be
+   *                        determined, therefore do not rely on restrictions 
of dnd-allowed-type.
+   *
+   * CSS classes:
+   * - dndPlaceholder       When an element is dragged over the list, a new 
placeholder child
+   *                        element will be added. This element is of type li 
and has the class
+   *                        dndPlaceholder set. Alternatively, you can define 
your own placeholder
+   *                        by creating a child element with dndPlaceholder 
class.
+   * - dndDragover          Will be added to the list while an element is 
dragged over the list.
+   */
+  .directive('dndList', ['$parse', '$timeout', 'dndDropEffectWorkaround', 
'dndDragTypeWorkaround',
+                 function($parse,   $timeout,   dndDropEffectWorkaround,   
dndDragTypeWorkaround) {
+    return function(scope, element, attr) {
+      // While an element is dragged over the list, this placeholder element 
is inserted
+      // at the location where the element would be inserted after dropping
+      var placeholder = getPlaceholderElement();
+      var placeholderNode = placeholder[0];
+      var listNode = element[0];
+      placeholder.remove();
+
+      var horizontal = attr.dndHorizontalList && 
scope.$eval(attr.dndHorizontalList);
+      var externalSources = attr.dndExternalSources && 
scope.$eval(attr.dndExternalSources);
+
+      /**
+       * The dragenter event is fired when a dragged element or text selection 
enters a valid drop
+       * target. According to the spec, we either need to have a dropzone 
attribute or listen on
+       * dragenter events and call preventDefault(). It should be noted though 
that no browser seems
+       * to enforce this behaviour.
+       */
+      element.on('dragenter', function (event) {
+        event = event.originalEvent || event;
+        if (!isDropAllowed(event)) return true;
+        event.preventDefault();
+      });
+
+      /**
+       * The dragover event is triggered "every few hundred milliseconds" 
while an element
+       * is being dragged over our list, or over an child element.
+       */
+      element.on('dragover', function(event) {
+        event = event.originalEvent || event;
+
+        if (!isDropAllowed(event)) return true;
+
+        // First of all, make sure that the placeholder is shown
+        // This is especially important if the list is empty
+        if (placeholderNode.parentNode != listNode) {
+          element.append(placeholder);
+        }
+
+        if (event.target !== listNode) {
+          // Try to find the node direct directly below the list node.
+          var listItemNode = event.target;
+          while (listItemNode.parentNode !== listNode && 
listItemNode.parentNode) {
+            listItemNode = listItemNode.parentNode;
+          }
+
+          if (listItemNode.parentNode === listNode && listItemNode !== 
placeholderNode) {
+            // If the mouse pointer is in the upper half of the child element,
+            // we place it before the child element, otherwise below it.
+            if (isMouseInFirstHalf(event, listItemNode)) {
+              listNode.insertBefore(placeholderNode, listItemNode);
+            } else {
+              listNode.insertBefore(placeholderNode, listItemNode.nextSibling);
+            }
+          }
+        } else {
+          // This branch is reached when we are dragging directly over the 
list element.
+          // Usually we wouldn't need to do anything here, but the IE does not 
fire it's
+          // events for the child element, only for the list directly. 
Therefore, we repeat
+          // the positioning algorithm for IE here.
+          if (isMouseInFirstHalf(event, placeholderNode, true)) {
+            // Check if we should move the placeholder element one spot 
towards the top.
+            // Note that display none elements will have offsetTop and 
offsetHeight set to
+            // zero, therefore we need a special check for them.
+            while (placeholderNode.previousElementSibling
+                 && (isMouseInFirstHalf(event, 
placeholderNode.previousElementSibling, true)
+                 || placeholderNode.previousElementSibling.offsetHeight === 
0)) {
+              listNode.insertBefore(placeholderNode, 
placeholderNode.previousElementSibling);
+            }
+          } else {
+            // Check if we should move the placeholder element one spot 
towards the bottom
+            while (placeholderNode.nextElementSibling &&
+                 !isMouseInFirstHalf(event, 
placeholderNode.nextElementSibling, true)) {
+              listNode.insertBefore(placeholderNode,
+                  placeholderNode.nextElementSibling.nextElementSibling);
+            }
+          }
+        }
+
+        // At this point we invoke the callback, which still can disallow the 
drop.
+        // We can't do this earlier because we want to pass the index of the 
placeholder.
+        if (attr.dndDragover && !invokeCallback(attr.dndDragover, event, 
getPlaceholderIndex())) {
+          return stopDragover();
+        }
+
+        element.addClass("dndDragover");
+        event.preventDefault();
+        event.stopPropagation();
+        return false;
+      });
+
+      /**
+       * When the element is dropped, we use the position of the placeholder 
element as the
+       * position where we insert the transferred data. This assumes that the 
list has exactly
+       * one child element per array element.
+       */
+      element.on('drop', function(event) {
+        event = event.originalEvent || event;
+
+        if (!isDropAllowed(event)) return true;
+
+        // The default behavior in Firefox is to interpret the dropped element 
as URL and
+        // forward to it. We want to prevent that even if our drop is aborted.
+        event.preventDefault();
+
+        // Unserialize the data that was serialized in dragstart. According to 
the HTML5 specs,
+        // the "Text" drag type will be converted to text/plain, but IE does 
not do that.
+        var data = event.dataTransfer.getData("Text") || 
event.dataTransfer.getData("text/plain");
+        var transferredObject;
+        try {
+          transferredObject = JSON.parse(data);
+        } catch(e) {
+          return stopDragover();
+        }
+
+        // Invoke the callback, which can transform the transferredObject and 
even abort the drop.
+        var index = getPlaceholderIndex();
+        if (attr.dndDrop) {
+          transferredObject = invokeCallback(attr.dndDrop, event, index, 
transferredObject);
+          if (!transferredObject) {
+            return stopDragover();
+          }
+        }
+
+        // Insert the object into the array, unless dnd-drop took care of that 
(returned true).
+        if (transferredObject !== true) {
+          scope.$apply(function() {
+            scope.$eval(attr.dndList).splice(index, 0, transferredObject);
+          });
+        }
+        invokeCallback(attr.dndInserted, event, index, transferredObject);
+
+        // In Chrome on Windows the dropEffect will always be none...
+        // We have to determine the actual effect manually from the allowed 
effects
+        if (event.dataTransfer.dropEffect === "none") {
+          if (event.dataTransfer.effectAllowed === "copy" ||
+              event.dataTransfer.effectAllowed === "move") {
+            dndDropEffectWorkaround.dropEffect = 
event.dataTransfer.effectAllowed;
+          } else {
+            dndDropEffectWorkaround.dropEffect = event.ctrlKey ? "copy" : 
"move";
+          }
+        } else {
+          dndDropEffectWorkaround.dropEffect = event.dataTransfer.dropEffect;
+        }
+
+        // Clean up
+        stopDragover();
+        event.stopPropagation();
+        return false;
+      });
+
+      /**
+       * We have to remove the placeholder when the element is no longer 
dragged over our list. The
+       * problem is that the dragleave event is not only fired when the 
element leaves our list,
+       * but also when it leaves a child element -- so practically it's fired 
all the time. As a
+       * workaround we wait a few milliseconds and then check if the 
dndDragover class was added
+       * again. If it is there, dragover must have been called in the 
meantime, i.e. the element
+       * is still dragging over the list. If you know a better way of doing 
this, please tell me!
+       */
+      element.on('dragleave', function(event) {
+        event = event.originalEvent || event;
+
+        element.removeClass("dndDragover");
+        $timeout(function() {
+          if (!element.hasClass("dndDragover")) {
+            placeholder.remove();
+          }
+        }, 100);
+      });
+
+      /**
+       * Checks whether the mouse pointer is in the first half of the given 
target element.
+       *
+       * In Chrome we can just use offsetY, but in Firefox we have to use 
layerY, which only
+       * works if the child element has position relative. In IE the events 
are only triggered
+       * on the listNode instead of the listNodeItem, therefore the mouse 
positions are
+       * relative to the parent element of targetNode.
+       */
+      function isMouseInFirstHalf(event, targetNode, relativeToParent) {
+        var mousePointer = horizontal ? (event.offsetX || event.layerX)
+                                      : (event.offsetY || event.layerY);
+        var targetSize = horizontal ? targetNode.offsetWidth : 
targetNode.offsetHeight;
+        var targetPosition = horizontal ? targetNode.offsetLeft : 
targetNode.offsetTop;
+        targetPosition = relativeToParent ? targetPosition : 0;
+        return mousePointer < targetPosition + targetSize / 2;
+      }
+
+      /**
+       * Tries to find a child element that has the dndPlaceholder class set. 
If none was found, a
+       * new li element is created.
+       */
+      function getPlaceholderElement() {
+        var placeholder;
+        angular.forEach(element.children(), function(childNode) {
+          var child = angular.element(childNode);
+          if (child.hasClass('dndPlaceholder')) {
+            placeholder = child;
+          }
+        });
+        return placeholder || angular.element("<li 
class='dndPlaceholder'></li>");
+      }
+
+      /**
+       * We use the position of the placeholder node to determine at which 
position of the array the
+       * object needs to be inserted
+       */
+      function getPlaceholderIndex() {
+        return Array.prototype.indexOf.call(listNode.children, 
placeholderNode);
+      }
+
+      /**
+       * Checks various conditions that must be fulfilled for a drop to be 
allowed
+       */
+      function isDropAllowed(event) {
+        // Disallow drop from external source unless it's allowed explicitly.
+        if (!dndDragTypeWorkaround.isDragging && !externalSources) return 
false;
+
+        // Check mimetype. Usually we would use a custom drag type instead of 
Text, but IE doesn't
+        // support that.
+        if (!hasTextMimetype(event.dataTransfer.types)) return false;
+
+        // Now check the dnd-allowed-types against the type of the incoming 
element. For drops from
+        // external sources we don't know the type, so it will need to be 
checked via dnd-drop.
+        if (attr.dndAllowedTypes && dndDragTypeWorkaround.isDragging) {
+          var allowed = scope.$eval(attr.dndAllowedTypes);
+          if (angular.isArray(allowed) && 
allowed.indexOf(dndDragTypeWorkaround.dragType) === -1) {
+            return false;
+          }
+        }
+
+        // Check whether droping is disabled completely
+        if (attr.dndDisableIf && scope.$eval(attr.dndDisableIf)) return false;
+
+        return true;
+      }
+
+      /**
+       * Small helper function that cleans up if we aborted a drop.
+       */
+      function stopDragover() {
+        placeholder.remove();
+        element.removeClass("dndDragover");
+        return true;
+      }
+
+      /**
+       * Invokes a callback with some interesting parameters and returns the 
callbacks return value.
+       */
+      function invokeCallback(expression, event, index, item) {
+        return $parse(expression)(scope, {
+          event: event,
+          index: index,
+          item: item || undefined,
+          external: !dndDragTypeWorkaround.isDragging,
+          type: dndDragTypeWorkaround.isDragging ? 
dndDragTypeWorkaround.dragType : undefined
+        });
+      }
+
+      /**
+       * Check if the dataTransfer object contains a drag type that we can 
handle. In old versions
+       * of IE the types collection will not even be there, so we just assume 
a drop is possible.
+       */
+      function hasTextMimetype(types) {
+        if (!types) return true;
+        for (var i = 0; i < types.length; i++) {
+          if (types[i] === "Text" || types[i] === "text/plain") return true;
+        }
+
+        return false;
+      }
+    };
+  }])
+
+  /**
+   * Use the dnd-nodrag attribute inside of dnd-draggable elements to prevent 
them from starting
+   * drag operations. This is especially useful if you want to use input 
elements inside of
+   * dnd-draggable elements or create specific handle elements. Note: This 
directive does not work
+   * in Internet Explorer 9.
+   */
+  .directive('dndNodrag', function() {
+    return function(scope, element, attr) {
+      // Set as draggable so that we can cancel the events explicitly
+      element.attr("draggable", "true");
+
+      /**
+       * Since the element is draggable, the browser's default operation is to 
drag it on dragstart.
+       * We will prevent that and also stop the event from bubbling up.
+       */
+      element.on('dragstart', function(event) {
+        event = event.originalEvent || event;
+
+        if (!event._dndHandle) {
+          // If a child element already reacted to dragstart and set a 
dataTransfer object, we will
+          // allow that. For example, this is the case for user selections 
inside of input elements.
+          if (!(event.dataTransfer.types && event.dataTransfer.types.length)) {
+            event.preventDefault();
+          }
+          event.stopPropagation();
+        }
+      });
+
+      /**
+       * Stop propagation of dragend events, otherwise dnd-moved might be 
triggered and the element
+       * would be removed.
+       */
+      element.on('dragend', function(event) {
+        event = event.originalEvent || event;
+        if (!event._dndHandle) {
+          event.stopPropagation();
+        }
+      });
+    };
+  })
+
+  /**
+   * Use the dnd-handle directive within a dnd-nodrag element in order to 
allow dragging with that
+   * element after all. Therefore, by combining dnd-nodrag and dnd-handle you 
can allow
+   * dnd-draggable elements to only be dragged via specific "handle" elements. 
Note that Internet
+   * Explorer will show the handle element as drag image instead of the 
dnd-draggable element. You
+   * can work around this by styling the handle element differently when it is 
being dragged. Use
+   * the CSS selector .dndDragging:not(.dndDraggingSource) [dnd-handle] for 
that.
+   */
+  .directive('dndHandle', function() {
+    return function(scope, element, attr) {
+      element.attr("draggable", "true");
+
+      element.on('dragstart dragend', function(event) {
+        event = event.originalEvent || event;
+        event._dndHandle = true;
+      });
+    };
+  })
+
+  /**
+   * This workaround handles the fact that Internet Explorer does not support 
drag types other than
+   * "Text" and "URL". That means we can not know whether the data comes from 
one of our elements or
+   * is just some other data like a text selection. As a workaround we save 
the isDragging flag in
+   * here. When a dropover event occurs, we only allow the drop if we are 
already dragging, because
+   * that means the element is ours.
+   */
+  .factory('dndDragTypeWorkaround', function(){ return {} })
+
+  /**
+   * Chrome on Windows does not set the dropEffect field, which we need in 
dragend to determine
+   * whether a drag operation was successful. Therefore we have to maintain it 
in this global
+   * variable. The bug report for that has been open for years:
+   * https://code.google.com/p/chromium/issues/detail?id=39399
+   */
+  .factory('dndDropEffectWorkaround', function(){ return {} });
+
+/* nvd3 version 1.8.4 (https://github.com/novus/nvd3) 2016-07-03 */
+(function(){
+
+// set up main nv object
+var nv = {};
+
+// the major global objects under the nv namespace
+nv.dev = false; //set false when in production
+nv.tooltip = nv.tooltip || {}; // For the tooltip system
+nv.utils = nv.utils || {}; // Utility subsystem
+nv.models = nv.models || {}; //stores all the possible models/components
+nv.charts = {}; //stores all the ready to use charts
+nv.logs = {}; //stores some statistics and potential error messages
+nv.dom = {}; //DOM manipulation functions
+
+// Node/CommonJS - require D3
+if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined' && 
typeof(d3) == 'undefined') {
+    d3 = require('d3');
+}
+
+nv.dispatch = d3.dispatch('render_start', 'render_end');
+
+// Function bind polyfill
+// Needed ONLY for phantomJS as it's missing until version 2.0 which is 
unreleased as of this comment
+// https://github.com/ariya/phantomjs/issues/10522
+// http://kangax.github.io/compat-table/es5/#Function.prototype.bind
+// phantomJS is used for running the test suite
+if (!Function.prototype.bind) {
+    Function.prototype.bind = function (oThis) {
+        if (typeof this !== "function") {
+            // closest thing possible to the ECMAScript 5 internal IsCallable 
function
+            throw new TypeError("Function.prototype.bind - what is trying to 
be bound is not callable");
+        }
+
+        var aArgs = Array.prototype.slice.call(arguments, 1),
+            fToBind = this,
+            fNOP = function () {},
+            fBound = function () {
+                return fToBind.apply(this instanceof fNOP && oThis
+                        ? this
+                        : oThis,
+                    aArgs.concat(Array.prototype.slice.call(arguments)));
+            };
+
+        fNOP.prototype = this.prototype;
+        fBound.prototype = new fNOP();
+        return fBound;
+    };
+}
+
+//  Development render timers - disabled if dev = false
+if (nv.dev) {
+    nv.dispatch.on('render_start', function(e) {
+        nv.logs.startTime = +new Date();
+    });
+
+    nv.dispatch.on('render_end', function(e) {
+        nv.logs.endTime = +new Date();
+        nv.logs.totalTime = nv.logs.endTime - nv.logs.startTime;
+        nv.log('total', nv.logs.totalTime); // used for development, to keep 
track of graph generation times
+    });
+}
+
+// Logs all arguments, and returns the last so you can test things in place
+// Note: in IE8 console.log is an object not a function, and if modernizr is 
used
+// then calling Function.prototype.bind with with anything other than a 
function
+// causes a TypeError to be thrown.
+nv.log = function() {
+    if (nv.dev && window.console && console.log && console.log.apply)
+        console.log.apply(console, arguments);
+    else if (nv.dev && window.console && typeof console.log == "function" && 
Function.prototype.bind) {
+        var log = Function.prototype.bind.call(console.log, console);
+        log.apply(console, arguments);
+    }
+    return arguments[arguments.length - 1];
+};
+
+// print console warning, should be used by deprecated functions
+nv.deprecated = function(name, info) {
+    if (console && console.warn) {
+        console.warn('nvd3 warning: `' + name + '` has been deprecated. ', 
info || '');
+    }
+};
+
+// The nv.render function is used to queue up chart rendering
+// in non-blocking async functions.
+// When all queued charts are done rendering, nv.dispatch.render_end is 
invoked.
+nv.render = function render(step) {
+    // number of graphs to generate in each timeout loop
+    step = step || 1;
+
+    nv.render.active = true;
+    nv.dispatch.render_start();
+
+    var renderLoop = function() {
+        var chart, graph;
+
+        for (var i = 0; i < step && (graph = nv.render.queue[i]); i++) {
+            chart = graph.generate();
+            if (typeof graph.callback == typeof(Function)) 
graph.callback(chart);
+        }
+
+        nv.render.queue.splice(0, i);
+
+        if (nv.render.queue.length) {
+            setTimeout(renderLoop);
+        }
+        else {
+            nv.dispatch.render_end();
+            nv.render.active = false;
+        }
+    };
+
+    setTimeout(renderLoop);
+};
+
+nv.render.active = false;
+nv.render.queue = [];
+
+/*
+Adds a chart to the async rendering queue. This method can take arguments in 
two forms:
+nv.addGraph({
+    generate: <Function>
+    callback: <Function>
+})
+
+or
+
+nv.addGraph(<generate Function>, <callback Function>)
+
+The generate function should contain code that creates the NVD3 model, sets 
options
+on it, adds data to an SVG element, and invokes the chart model. The generate 
function
+should return the chart model.  See examples/lineChart.html for a usage 
example.
+
+The callback function is optional, and it is called when the generate function 
completes.
+*/
+nv.addGraph = function(obj) {
+    if (typeof arguments[0] === typeof(Function)) {
+        obj = {generate: arguments[0], callback: arguments[1]};
+    }
+
+    nv.render.queue.push(obj);
+
+    if (!nv.render.active) {
+        nv.render();
+    }
+};
+
+// Node/CommonJS exports
+if (typeof(module) !== 'undefined' && typeof(exports) !== 'undefined') {
+  module.exports = nv;
+}
+
+if (typeof(window) !== 'undefined') {
+  window.nv = nv;
+}
+/* Facade for queueing DOM write operations
+ * with Fastdom (https://github.com/wilsonpage/fastdom)
+ * if available.
+ * This could easily be extended to support alternate
+ * implementations in the future.
+ */
+nv.dom.write = function(callback) {
+       if (window.fastdom !== undefined) {
+               return fastdom.mutate(callback);
+       }
+       return callback();
+};
+
+/* Facade for queueing DOM read operations
+ * with Fastdom (https://github.com/wilsonpage/fastdom)
+ * if available.
+ * This could easily be extended to support alternate
+ * implementations in the future.
+ */
+nv.dom.read = function(callback) {
+       if (window.fastdom !== undefined) {
+               return fastdom.measure(callback);
+       }
+       return callback();
+};
+/* Utility class to handle creation of an interactive layer.
+ This places a rectangle on top of the chart. When you mouse move over it, it 
sends a dispatch
+ containing the X-coordinate. It can also render a vertical line where the 
mouse is located.
+
+ dispatch.elementMousemove is the important event to latch onto.  It is fired 
whenever the mouse moves over
+ the rectangle. The dispatch is given one object which contains the mouseX/Y 
location.
+ It also has 'pointXValue', which is the conversion of mouseX to the x-axis 
scale.
+ */
+nv.interactiveGuideline = function() {
+    "use strict";
+
+    var margin = { left: 0, top: 0 } //Pass the chart's top and left magins. 
Used to calculate the mouseX/Y.
+        ,   width = null
+        ,   height = null
+        ,   xScale = d3.scale.linear()
+        ,   dispatch = d3.dispatch('elementMousemove', 'elementMouseout', 
'elementClick', 'elementDblclick', 'elementMouseDown', 'elementMouseUp')
+        ,   showGuideLine = true
+        ,   svgContainer = null // Must pass the chart's svg, we'll use its 
mousemove event.
+        ,   tooltip = nv.models.tooltip()
+        ,   isMSIE =  window.ActiveXObject// Checkt if IE by looking for 
activeX. (excludes IE11)
+    ;
+
+    tooltip
+        .duration(0)
+        .hideDelay(0)
+        .hidden(false);
+
+    function layer(selection) {
+        selection.each(function(data) {
+            var container = d3.select(this);
+            var availableWidth = (width || 960), availableHeight = (height || 
400);
+            var wrap = container.selectAll("g.nv-wrap.nv-interactiveLineLayer")
+                .data([data]);
+            var wrapEnter = wrap.enter()
+                .append("g").attr("class", " nv-wrap nv-interactiveLineLayer");
+            wrapEnter.append("g").attr("class","nv-interactiveGuideLine");
+
+            if (!svgContainer) {
+                return;
+            }
+
+            function mouseHandler() {
+                var d3mouse = d3.mouse(this);
+                var mouseX = d3mouse[0];
+                var mouseY = d3mouse[1];
+                var subtractMargin = true;
+                var mouseOutAnyReason = false;
+                if (isMSIE) {
+                    /*
+                     D3.js (or maybe SVG.getScreenCTM) has a nasty bug in 
Internet Explorer 10.
+                     d3.mouse() returns incorrect X,Y mouse coordinates when 
mouse moving
+                     over a rect in IE 10.
+                     However, d3.event.offsetX/Y also returns the mouse 
coordinates
+                     relative to the triggering <rect>. So we use offsetX/Y on 
IE.
+                     */
+                    mouseX = d3.event.offsetX;
+                    mouseY = d3.event.offsetY;
+
+                    /*
+                     On IE, if you attach a mouse event listener to the <svg> 
container,
+                     it will actually trigger it for all the child elements 
(like <path>, <circle>, etc).
+                     When this happens on IE, the offsetX/Y is set to where 
ever the child element
+                     is located.
+                     As a result, we do NOT need to subtract margins to figure 
out the mouse X/Y
+                     position under this scenario. Removing the line below 
*will* cause
+                     the interactive layer to not work right on IE.
+                     */
+                    if(d3.event.target.tagName !== "svg") {
+                        subtractMargin = false;
+                    }
+
+                    if (d3.event.target.className.baseVal.match("nv-legend")) {
+                        mouseOutAnyReason = true;
+                    }
+
+                }
+
+                if(subtractMargin) {
+                    mouseX -= margin.left;
+                    mouseY -= margin.top;
+                }
+
+                /* If mouseX/Y is outside of the chart's bounds,
+                 trigger a mouseOut event.
+                 */
+                if (d3.event.type === 'mouseout'
+                    || mouseX < 0 || mouseY < 0
+                    || mouseX > availableWidth || mouseY > availableHeight
+                    || (d3.event.relatedTarget && 
d3.event.relatedTarget.ownerSVGElement === undefined)
+                    || mouseOutAnyReason
+                    ) {
+
+                    if (isMSIE) {
+                        if (d3.event.relatedTarget
+                            && d3.event.relatedTarget.ownerSVGElement === 
undefined
+                            && (d3.event.relatedTarget.className === undefined
+                                || 
d3.event.relatedTarget.className.match(tooltip.nvPointerEventsClass))) {
+
+                            return;
+                        }
+                    }
+                    dispatch.elementMouseout({
+                        mouseX: mouseX,
+                        mouseY: mouseY
+                    });
+                    layer.renderGuideLine(null); //hide the guideline
+                    tooltip.hidden(true);
+                    return;
+                } else {
+                    tooltip.hidden(false);
+                }
+
+
+                var scaleIsOrdinal = typeof xScale.rangeBands === 'function';
+                var pointXValue = undefined;
+
+                // Ordinal scale has no invert method
+                if (scaleIsOrdinal) {
+                    var elementIndex = d3.bisect(xScale.range(), mouseX) - 1;
+                    // Check if mouseX is in the range band
+                    if (xScale.range()[elementIndex] + xScale.rangeBand() >= 
mouseX) {
+                        pointXValue = 
xScale.domain()[d3.bisect(xScale.range(), mouseX) - 1];
+                    }
+                    else {
+                        dispatch.elementMouseout({
+                            mouseX: mouseX,
+                            mouseY: mouseY
+                        });
+                        layer.renderGuideLine(null); //hide the guideline
+                        tooltip.hidden(true);
+                        return;
+                    }
+                }
+                else {
+                    pointXValue = xScale.invert(mouseX);
+                }
+
+                dispatch.elementMousemove({
+                    mouseX: mouseX,
+                    mouseY: mouseY,
+                    pointXValue: pointXValue
+                });
+
+                //If user double clicks the layer, fire a elementDblclick
+                if (d3.event.type === "dblclick") {
+                    dispatch.elementDblclick({
+                        mouseX: mouseX,
+                        mouseY: mouseY,
+                        pointXValue: pointXValue
+                    });
+                }
+
+                // if user single clicks the layer, fire elementClick
+                if (d3.event.type === 'click') {
+                    dispatch.elementClick({
+                        mouseX: mouseX,
+                        mouseY: mouseY,
+                        pointXValue: pointXValue
+                    });
+                }
+
+                // if user presses mouse down the layer, fire elementMouseDown
+                if (d3.event.type === 'mousedown') {
+                       dispatch.elementMouseDown({
+                               mouseX: mouseX,
+                               mouseY: mouseY,
+                               pointXValue: pointXValue
+                       });
+                }
+
+                // if user presses mouse down the layer, fire elementMouseUp
+                if (d3.event.type === 'mouseup') {
+                       dispatch.elementMouseUp({
+                               mouseX: mouseX,
+                               mouseY: mouseY,
+                               pointXValue: pointXValue
+                       });
+                }
+            }
+
+            svgContainer
+                .on("touchmove",mouseHandler)
+                .on("mousemove",mouseHandler, true)
+                .on("mouseout" ,mouseHandler,true)
+                .on("mousedown" ,mouseHandler,true)
+                .on("mouseup" ,mouseHandler,true)
+                .on("dblclick" ,mouseHandler)
+                .on("click", mouseHandler)
+            ;
+
+            layer.guideLine = null;
+            //Draws a vertical guideline at the given X postion.
+            layer.renderGuideLine = function(x) {
+                if (!showGuideLine) return;
+                if (layer.guideLine && layer.guideLine.attr("x1") === x) 
return;
+                nv.dom.write(function() {
+                    var line = wrap.select(".nv-interactiveGuideLine")
+                        .selectAll("line")
+                        .data((x != null) ? [nv.utils.NaNtoZero(x)] : [], 
String);
+                    line.enter()
+                        .append("line")
+                        .attr("class", "nv-guideline")
+                        .attr("x1", function(d) { return d;})
+                        .attr("x2", function(d) { return d;})
+                        .attr("y1", availableHeight)
+                        .attr("y2",0);
+                    line.exit().remove();
+                });
+            }
+        });
+    }
+
+    layer.dispatch = dispatch;
+    layer.tooltip = tooltip;
+
+    layer.margin = function(_) {
+        if (!arguments.length) return margin;
+        margin.top    = typeof _.top    != 'undefined' ? _.top    : margin.top;
+        margin.left   = typeof _.left   != 'undefined' ? _.left   : 
margin.left;
+        return layer;
+    };
+
+    layer.width = function(_) {
+        if (!arguments.length) return width;
+        width = _;
+        return layer;
+    };
+
+    layer.height = function(_) {
+        if (!arguments.length) return height;
+        height = _;
+        return layer;
+    };
+
+    layer.xScale = function(_) {
+        if (!arguments.length) return xScale;
+        xScale = _;
+        return layer;
+    };
+
+    layer.showGuideLine = function(_) {
+        if (!arguments.length) return showGuideLine;
+        showGuideLine = _;
+        return layer;
+    };
+
+    layer.svgContainer = function(_) {
+        if (!arguments.length) return svgContainer;
+        svgContainer = _;
+        return layer;
+    };
+
+    return layer;
+};
+
+/* Utility class that uses d3.bisect to find the index in a given array, where 
a search value can be inserted.
+ This is different from normal bisectLeft; this function finds the nearest 
index to insert the search value.
+
+ For instance, lets say your array is [1,2,3,5,10,30], and you search for 28.
+ Normal d3.bisectLeft will return 4, because 28 is inserted after the number 
10.  But interactiveBisect will return 5
+ because 28 is closer to 30 than 10.
+
+ Unit tests can be found in: interactiveBisectTest.html
+
+ Has the following known issues:
+ * Will not work if the data points move backwards (ie, 10,9,8,7, etc) or if 
the data points are in random order.
+ * Won't work if there are duplicate x coordinate values.
+ */
+nv.interactiveBisect = function (values, searchVal, xAccessor) {
+    "use strict";
+    if (! (values instanceof Array)) {
+        return null;
+    }
+    var _xAccessor;
+    if (typeof xAccessor !== 'function') {
+        _xAccessor = function(d) {
+            return d.x;
+        }
+    } else {
+        _xAccessor = xAccessor;
+    }
+    var _cmp = function(d, v) {
+        // Accessors are no longer passed the index of the element along with
+        // the element itself when invoked by d3.bisector.
+        //
+        // Starting at D3 v3.4.4, d3.bisector() started inspecting the
+        // function passed to determine if it should consider it an accessor
+        // or a comparator. This meant that accessors that take two arguments
+        // (expecting an index as the second parameter) are treated as
+        // comparators where the second argument is the search value against
+        // which the first argument is compared.
+        return _xAccessor(d) - v;
+    };
+
+    var bisect = d3.bisector(_cmp).left;
+    var index = d3.max([0, bisect(values,searchVal) - 1]);
+    var currentValue = _xAccessor(values[index]);
+
+    if (typeof currentValue === 'undefined') {
+        currentValue = index;
+    }
+
+    if (currentValue === searchVal) {
+        return index; //found exact match
+    }
+
+    var nextIndex = d3.min([index+1, values.length - 1]);
+    var nextValue = _xAccessor(values[nextIndex]);
+
+    if (typeof nextValue === 'undefined') {
+        nextValue = nextIndex;
+    }
+
+    if (Math.abs(nextValue - searchVal) >= Math.abs(currentValue - searchVal)) 
{
+        return index;
+    } else {
+        return nextIndex
+    }
+};
+
+/*
+ Returns the index in the array "values" that is closest to searchVal.
+ Only returns an index if searchVal is within some "threshold".
+ Otherwise, returns null.
+ */
+nv.nearestValueIndex = function (values, searchVal, threshold) {
+    "use strict";
+    var yDistMax = Infinity, indexToHighlight = null;
+    values.forEach(function(d,i) {
+        var delta = Math.abs(searchVal - d);
+        if ( d != null && delta <= yDistMax && delta < threshold) {
+            yDistMax = delta;
+            indexToHighlight = i;
+        }
+    });
+    return indexToHighlight;
+};
+
+/* Model which can be instantiated to handle tooltip rendering.
+ Example usage:
+ var tip = nv.models.tooltip().gravity('w').distance(23)
+ .data(myDataObject);
+
+ tip();    //just invoke the returned function to render tooltip.
+ */
+nv.models.tooltip = function() {
+    "use strict";
+
+    /*
+    Tooltip data. If data is given in the proper format, a consistent tooltip 
is generated.
+    Example Format of data:
+    {
+        key: "Date",
+        value: "August 2009",
+        series: [
+            {key: "Series 1", value: "Value 1", color: "#000"},
+            {key: "Series 2", value: "Value 2", color: "#00f"}
+        ]
+    }
+    */
+    var id = "nvtooltip-" + Math.floor(Math.random() * 100000) // Generates a 
unique id when you create a new tooltip() object.
+        ,   data = null
+        ,   gravity = 'w'   // Can be 'n','s','e','w'. Determines how tooltip 
is positioned.
+        ,   distance = 25 // Distance to offset tooltip from the mouse 
location.
+        ,   snapDistance = 0   // Tolerance allowed before tooltip is moved 
from its current position (creates 'snapping' effect)
+        ,   classes = null  // Attaches additional CSS classes to the tooltip 
DIV that is created.
+      

<TRUNCATED>

Reply via email to