]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Make `Chart.Animation/animations/Tooltip` importable (#5382)
authorSimon Brunel <simonbrunel@users.noreply.github.com>
Mon, 2 Apr 2018 08:55:52 +0000 (10:55 +0200)
committerGitHub <noreply@github.com>
Mon, 2 Apr 2018 08:55:52 +0000 (10:55 +0200)
.htmllintrc
src/chart.js
src/core/core.animation.js
src/core/core.animations.js [new file with mode: 0644]
src/core/core.controller.js
src/core/core.tooltip.js
test/specs/global.defaults.tests.js
test/specs/global.namespace.tests.js [new file with mode: 0644]

index a6b2097031b1e3bf5e906cbc3fb53a191ec52cc2..1ab933490deaf7d499dce14c9aa88b32e9d1e889 100644 (file)
@@ -1,5 +1,6 @@
 {
        "indent-style": "tabs",
+       "line-end-style": false,
        "attr-quote-style": "double",
        "spec-char-escape": false,
        "attr-bans": [
index a958e343ff8eed56d7f9dc6b774552ae9f3e6f61..04d7df6c99a0777cb204fa8240105c40b3be81c6 100644 (file)
@@ -8,6 +8,8 @@ Chart.helpers = require('./helpers/index');
 // @todo dispatch these helpers into appropriated helpers/helpers.* file and write unit tests!
 require('./core/core.helpers')(Chart);
 
+Chart.Animation = require('./core/core.animation');
+Chart.animationService = require('./core/core.animations');
 Chart.defaults = require('./core/core.defaults');
 Chart.Element = require('./core/core.element');
 Chart.elements = require('./elements/index');
@@ -16,13 +18,12 @@ Chart.layouts = require('./core/core.layouts');
 Chart.platform = require('./platforms/platform');
 Chart.plugins = require('./core/core.plugins');
 Chart.Ticks = require('./core/core.ticks');
+Chart.Tooltip = require('./core/core.tooltip');
 
-require('./core/core.animation')(Chart);
 require('./core/core.controller')(Chart);
 require('./core/core.datasetController')(Chart);
 require('./core/core.scaleService')(Chart);
 require('./core/core.scale')(Chart);
-require('./core/core.tooltip')(Chart);
 
 require('./scales/scale.linearbase')(Chart);
 require('./scales/scale.category')(Chart);
index af746588e60f4b455a514f0b2a8edeb088cf94b1..8b2f4dd2ade908bea01f8be67d01a1840f0f9d38 100644 (file)
-/* global window: false */
 'use strict';
 
-var defaults = require('./core.defaults');
 var Element = require('./core.element');
-var helpers = require('../helpers/index');
 
-defaults._set('global', {
-       animation: {
-               duration: 1000,
-               easing: 'easeOutQuart',
-               onProgress: helpers.noop,
-               onComplete: helpers.noop
-       }
-});
-
-module.exports = function(Chart) {
-
-       Chart.Animation = Element.extend({
-               chart: null, // the animation associated chart instance
-               currentStep: 0, // the current animation step
-               numSteps: 60, // default number of steps
-               easing: '', // the easing to use for this animation
-               render: null, // render function used by the animation service
-
-               onAnimationProgress: null, // user specified callback to fire on each step of the animation
-               onAnimationComplete: null, // user specified callback to fire when the animation finishes
-       });
-
-       Chart.animationService = {
-               frameDuration: 17,
-               animations: [],
-               dropFrames: 0,
-               request: null,
-
-               /**
-                * @param {Chart} chart - The chart to animate.
-                * @param {Chart.Animation} animation - The animation that we will animate.
-                * @param {Number} duration - The animation duration in ms.
-                * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
-                */
-               addAnimation: function(chart, animation, duration, lazy) {
-                       var animations = this.animations;
-                       var i, ilen;
-
-                       animation.chart = chart;
-
-                       if (!lazy) {
-                               chart.animating = true;
-                       }
-
-                       for (i = 0, ilen = animations.length; i < ilen; ++i) {
-                               if (animations[i].chart === chart) {
-                                       animations[i] = animation;
-                                       return;
-                               }
-                       }
-
-                       animations.push(animation);
-
-                       // If there are no animations queued, manually kickstart a digest, for lack of a better word
-                       if (animations.length === 1) {
-                               this.requestAnimationFrame();
-                       }
-               },
-
-               cancelAnimation: function(chart) {
-                       var index = helpers.findIndex(this.animations, function(animation) {
-                               return animation.chart === chart;
-                       });
-
-                       if (index !== -1) {
-                               this.animations.splice(index, 1);
-                               chart.animating = false;
-                       }
-               },
-
-               requestAnimationFrame: function() {
-                       var me = this;
-                       if (me.request === null) {
-                               // Skip animation frame requests until the active one is executed.
-                               // This can happen when processing mouse events, e.g. 'mousemove'
-                               // and 'mouseout' events will trigger multiple renders.
-                               me.request = helpers.requestAnimFrame.call(window, function() {
-                                       me.request = null;
-                                       me.startDigest();
-                               });
-                       }
-               },
-
-               /**
-                * @private
-                */
-               startDigest: function() {
-                       var me = this;
-                       var startTime = Date.now();
-                       var framesToDrop = 0;
+var exports = module.exports = Element.extend({
+       chart: null, // the animation associated chart instance
+       currentStep: 0, // the current animation step
+       numSteps: 60, // default number of steps
+       easing: '', // the easing to use for this animation
+       render: null, // render function used by the animation service
 
-                       if (me.dropFrames > 1) {
-                               framesToDrop = Math.floor(me.dropFrames);
-                               me.dropFrames = me.dropFrames % 1;
-                       }
-
-                       me.advance(1 + framesToDrop);
-
-                       var endTime = Date.now();
-
-                       me.dropFrames += (endTime - startTime) / me.frameDuration;
-
-                       // Do we have more stuff to animate?
-                       if (me.animations.length > 0) {
-                               me.requestAnimationFrame();
-                       }
-               },
-
-               /**
-                * @private
-                */
-               advance: function(count) {
-                       var animations = this.animations;
-                       var animation, chart;
-                       var i = 0;
-
-                       while (i < animations.length) {
-                               animation = animations[i];
-                               chart = animation.chart;
-
-                               animation.currentStep = (animation.currentStep || 0) + count;
-                               animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
-
-                               helpers.callback(animation.render, [chart, animation], chart);
-                               helpers.callback(animation.onAnimationProgress, [animation], chart);
-
-                               if (animation.currentStep >= animation.numSteps) {
-                                       helpers.callback(animation.onAnimationComplete, [animation], chart);
-                                       chart.animating = false;
-                                       animations.splice(i, 1);
-                               } else {
-                                       ++i;
-                               }
-                       }
-               }
-       };
-
-       /**
-        * Provided for backward compatibility, use Chart.Animation instead
-        * @prop Chart.Animation#animationObject
-        * @deprecated since version 2.6.0
-        * @todo remove at version 3
-        */
-       Object.defineProperty(Chart.Animation.prototype, 'animationObject', {
-               get: function() {
-                       return this;
-               }
-       });
+       onAnimationProgress: null, // user specified callback to fire on each step of the animation
+       onAnimationComplete: null, // user specified callback to fire when the animation finishes
+});
 
-       /**
-        * Provided for backward compatibility, use Chart.Animation#chart instead
-        * @prop Chart.Animation#chartInstance
-        * @deprecated since version 2.6.0
-        * @todo remove at version 3
-        */
-       Object.defineProperty(Chart.Animation.prototype, 'chartInstance', {
-               get: function() {
-                       return this.chart;
-               },
-               set: function(value) {
-                       this.chart = value;
-               }
-       });
+// DEPRECATIONS
+
+/**
+ * Provided for backward compatibility, use Chart.Animation instead
+ * @prop Chart.Animation#animationObject
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ */
+Object.defineProperty(exports.prototype, 'animationObject', {
+       get: function() {
+               return this;
+       }
+});
 
-};
+/**
+ * Provided for backward compatibility, use Chart.Animation#chart instead
+ * @prop Chart.Animation#chartInstance
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
+ */
+Object.defineProperty(exports.prototype, 'chartInstance', {
+       get: function() {
+               return this.chart;
+       },
+       set: function(value) {
+               this.chart = value;
+       }
+});
diff --git a/src/core/core.animations.js b/src/core/core.animations.js
new file mode 100644 (file)
index 0000000..6853cb8
--- /dev/null
@@ -0,0 +1,129 @@
+/* global window: false */
+'use strict';
+
+var defaults = require('./core.defaults');
+var helpers = require('../helpers/index');
+
+defaults._set('global', {
+       animation: {
+               duration: 1000,
+               easing: 'easeOutQuart',
+               onProgress: helpers.noop,
+               onComplete: helpers.noop
+       }
+});
+
+module.exports = {
+       frameDuration: 17,
+       animations: [],
+       dropFrames: 0,
+       request: null,
+
+       /**
+        * @param {Chart} chart - The chart to animate.
+        * @param {Chart.Animation} animation - The animation that we will animate.
+        * @param {Number} duration - The animation duration in ms.
+        * @param {Boolean} lazy - if true, the chart is not marked as animating to enable more responsive interactions
+        */
+       addAnimation: function(chart, animation, duration, lazy) {
+               var animations = this.animations;
+               var i, ilen;
+
+               animation.chart = chart;
+
+               if (!lazy) {
+                       chart.animating = true;
+               }
+
+               for (i = 0, ilen = animations.length; i < ilen; ++i) {
+                       if (animations[i].chart === chart) {
+                               animations[i] = animation;
+                               return;
+                       }
+               }
+
+               animations.push(animation);
+
+               // If there are no animations queued, manually kickstart a digest, for lack of a better word
+               if (animations.length === 1) {
+                       this.requestAnimationFrame();
+               }
+       },
+
+       cancelAnimation: function(chart) {
+               var index = helpers.findIndex(this.animations, function(animation) {
+                       return animation.chart === chart;
+               });
+
+               if (index !== -1) {
+                       this.animations.splice(index, 1);
+                       chart.animating = false;
+               }
+       },
+
+       requestAnimationFrame: function() {
+               var me = this;
+               if (me.request === null) {
+                       // Skip animation frame requests until the active one is executed.
+                       // This can happen when processing mouse events, e.g. 'mousemove'
+                       // and 'mouseout' events will trigger multiple renders.
+                       me.request = helpers.requestAnimFrame.call(window, function() {
+                               me.request = null;
+                               me.startDigest();
+                       });
+               }
+       },
+
+       /**
+        * @private
+        */
+       startDigest: function() {
+               var me = this;
+               var startTime = Date.now();
+               var framesToDrop = 0;
+
+               if (me.dropFrames > 1) {
+                       framesToDrop = Math.floor(me.dropFrames);
+                       me.dropFrames = me.dropFrames % 1;
+               }
+
+               me.advance(1 + framesToDrop);
+
+               var endTime = Date.now();
+
+               me.dropFrames += (endTime - startTime) / me.frameDuration;
+
+               // Do we have more stuff to animate?
+               if (me.animations.length > 0) {
+                       me.requestAnimationFrame();
+               }
+       },
+
+       /**
+        * @private
+        */
+       advance: function(count) {
+               var animations = this.animations;
+               var animation, chart;
+               var i = 0;
+
+               while (i < animations.length) {
+                       animation = animations[i];
+                       chart = animation.chart;
+
+                       animation.currentStep = (animation.currentStep || 0) + count;
+                       animation.currentStep = Math.min(animation.currentStep, animation.numSteps);
+
+                       helpers.callback(animation.render, [chart, animation], chart);
+                       helpers.callback(animation.onAnimationProgress, [animation], chart);
+
+                       if (animation.currentStep >= animation.numSteps) {
+                               helpers.callback(animation.onAnimationComplete, [animation], chart);
+                               chart.animating = false;
+                               animations.splice(i, 1);
+                       } else {
+                               ++i;
+                       }
+               }
+       }
+};
index e29a5b0769c6c0a4af4618e4edc75549390ad6bd..8f7d04fc64dea996ffb6d7ba96d7d4904137deb1 100644 (file)
@@ -1,11 +1,14 @@
 'use strict';
 
+var Animation = require('./core.animation');
+var animations = require('./core.animations');
 var defaults = require('./core.defaults');
 var helpers = require('../helpers/index');
 var Interaction = require('./core.interaction');
 var layouts = require('./core.layouts');
 var platform = require('../platforms/platform');
 var plugins = require('./core.plugins');
+var Tooltip = require('./core.tooltip');
 
 module.exports = function(Chart) {
 
@@ -164,7 +167,7 @@ module.exports = function(Chart) {
 
                stop: function() {
                        // Stops any current animation loop occurring
-                       Chart.animationService.cancelAnimation(this);
+                       animations.cancelAnimation(this);
                        return this;
                },
 
@@ -519,7 +522,7 @@ module.exports = function(Chart) {
                        };
 
                        if (animationOptions && ((typeof duration !== 'undefined' && duration !== 0) || (typeof duration === 'undefined' && animationOptions.duration !== 0))) {
-                               var animation = new Chart.Animation({
+                               var animation = new Animation({
                                        numSteps: (duration || animationOptions.duration) / 16.66, // 60 fps
                                        easing: config.easing || animationOptions.easing,
 
@@ -535,12 +538,12 @@ module.exports = function(Chart) {
                                        onAnimationComplete: onComplete
                                });
 
-                               Chart.animationService.addAnimation(me, animation, duration, lazy);
+                               animations.addAnimation(me, animation, duration, lazy);
                        } else {
                                me.draw();
 
                                // See https://github.com/chartjs/Chart.js/issues/3781
-                               onComplete(new Chart.Animation({numSteps: 0, chart: me}));
+                               onComplete(new Animation({numSteps: 0, chart: me}));
                        }
 
                        return me;
@@ -775,7 +778,7 @@ module.exports = function(Chart) {
 
                initToolTip: function() {
                        var me = this;
-                       me.tooltip = new Chart.Tooltip({
+                       me.tooltip = new Tooltip({
                                _chart: me,
                                _chartInstance: me, // deprecated, backward compatibility
                                _data: me.data,
index 9b09d76044369f649c03733115b23958f251abb8..3f9490f854626e8fed2f4acff3a69f5928814ea5 100644 (file)
@@ -96,853 +96,859 @@ defaults._set('global', {
        }
 });
 
-module.exports = function(Chart) {
-
+var positioners = {
        /**
-        * Helper method to merge the opacity into a color
-        */
-       function mergeOpacity(colorString, opacity) {
-               var color = helpers.color(colorString);
-               return color.alpha(opacity * color.alpha()).rgbaString();
-       }
+        * Average mode places the tooltip at the average position of the elements shown
+        * @function Chart.Tooltip.positioners.average
+        * @param elements {ChartElement[]} the elements being displayed in the tooltip
+        * @returns {Point} tooltip position
+        */
+       average: function(elements) {
+               if (!elements.length) {
+                       return false;
+               }
 
-       // Helper to push or concat based on if the 2nd parameter is an array or not
-       function pushOrConcat(base, toPush) {
-               if (toPush) {
-                       if (helpers.isArray(toPush)) {
-                               // base = base.concat(toPush);
-                               Array.prototype.push.apply(base, toPush);
-                       } else {
-                               base.push(toPush);
+               var i, len;
+               var x = 0;
+               var y = 0;
+               var count = 0;
+
+               for (i = 0, len = elements.length; i < len; ++i) {
+                       var el = elements[i];
+                       if (el && el.hasValue()) {
+                               var pos = el.tooltipPosition();
+                               x += pos.x;
+                               y += pos.y;
+                               ++count;
                        }
                }
 
-               return base;
-       }
-
-       // Private helper to create a tooltip item model
-       // @param element : the chart element (point, arc, bar) to create the tooltip item for
-       // @return : new tooltip item
-       function createTooltipItem(element) {
-               var xScale = element._xScale;
-               var yScale = element._yScale || element._scale; // handle radar || polarArea charts
-               var index = element._index;
-               var datasetIndex = element._datasetIndex;
-
                return {
-                       xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
-                       yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
-                       index: index,
-                       datasetIndex: datasetIndex,
-                       x: element._model.x,
-                       y: element._model.y
+                       x: Math.round(x / count),
+                       y: Math.round(y / count)
                };
-       }
+       },
 
        /**
-        * Helper to get the reset model for the tooltip
-        * @param tooltipOpts {Object} the tooltip options
+        * Gets the tooltip position nearest of the item nearest to the event position
+        * @function Chart.Tooltip.positioners.nearest
+        * @param elements {Chart.Element[]} the tooltip elements
+        * @param eventPosition {Point} the position of the event in canvas coordinates
+        * @returns {Point} the tooltip position
         */
-       function getBaseModel(tooltipOpts) {
-               var globalDefaults = defaults.global;
-               var valueOrDefault = helpers.valueOrDefault;
-
-               return {
-                       // Positioning
-                       xPadding: tooltipOpts.xPadding,
-                       yPadding: tooltipOpts.yPadding,
-                       xAlign: tooltipOpts.xAlign,
-                       yAlign: tooltipOpts.yAlign,
+       nearest: function(elements, eventPosition) {
+               var x = eventPosition.x;
+               var y = eventPosition.y;
+               var minDistance = Number.POSITIVE_INFINITY;
+               var i, len, nearestElement;
+
+               for (i = 0, len = elements.length; i < len; ++i) {
+                       var el = elements[i];
+                       if (el && el.hasValue()) {
+                               var center = el.getCenterPoint();
+                               var d = helpers.distanceBetweenPoints(eventPosition, center);
+
+                               if (d < minDistance) {
+                                       minDistance = d;
+                                       nearestElement = el;
+                               }
+                       }
+               }
 
-                       // Body
-                       bodyFontColor: tooltipOpts.bodyFontColor,
-                       _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
-                       _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
-                       _bodyAlign: tooltipOpts.bodyAlign,
-                       bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
-                       bodySpacing: tooltipOpts.bodySpacing,
-
-                       // Title
-                       titleFontColor: tooltipOpts.titleFontColor,
-                       _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
-                       _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
-                       titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
-                       _titleAlign: tooltipOpts.titleAlign,
-                       titleSpacing: tooltipOpts.titleSpacing,
-                       titleMarginBottom: tooltipOpts.titleMarginBottom,
+               if (nearestElement) {
+                       var tp = nearestElement.tooltipPosition();
+                       x = tp.x;
+                       y = tp.y;
+               }
 
-                       // Footer
-                       footerFontColor: tooltipOpts.footerFontColor,
-                       _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
-                       _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
-                       footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
-                       _footerAlign: tooltipOpts.footerAlign,
-                       footerSpacing: tooltipOpts.footerSpacing,
-                       footerMarginTop: tooltipOpts.footerMarginTop,
-
-                       // Appearance
-                       caretSize: tooltipOpts.caretSize,
-                       cornerRadius: tooltipOpts.cornerRadius,
-                       backgroundColor: tooltipOpts.backgroundColor,
-                       opacity: 0,
-                       legendColorBackground: tooltipOpts.multiKeyBackground,
-                       displayColors: tooltipOpts.displayColors,
-                       borderColor: tooltipOpts.borderColor,
-                       borderWidth: tooltipOpts.borderWidth
+               return {
+                       x: x,
+                       y: y
                };
        }
+};
 
-       /**
-        * Get the size of the tooltip
-        */
-       function getTooltipSize(tooltip, model) {
-               var ctx = tooltip._chart.ctx;
-
-               var height = model.yPadding * 2; // Tooltip Padding
-               var width = 0;
-
-               // Count of all lines in the body
-               var body = model.body;
-               var combinedBodyLength = body.reduce(function(count, bodyItem) {
-                       return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
-               }, 0);
-               combinedBodyLength += model.beforeBody.length + model.afterBody.length;
-
-               var titleLineCount = model.title.length;
-               var footerLineCount = model.footer.length;
-               var titleFontSize = model.titleFontSize;
-               var bodyFontSize = model.bodyFontSize;
-               var footerFontSize = model.footerFontSize;
-
-               height += titleLineCount * titleFontSize; // Title Lines
-               height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
-               height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
-               height += combinedBodyLength * bodyFontSize; // Body Lines
-               height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
-               height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
-               height += footerLineCount * (footerFontSize); // Footer Lines
-               height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
-
-               // Title width
-               var widthPadding = 0;
-               var maxLineWidth = function(line) {
-                       width = Math.max(width, ctx.measureText(line).width + widthPadding);
-               };
-
-               ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
-               helpers.each(model.title, maxLineWidth);
+/**
+ * Helper method to merge the opacity into a color
+ */
+function mergeOpacity(colorString, opacity) {
+       var color = helpers.color(colorString);
+       return color.alpha(opacity * color.alpha()).rgbaString();
+}
+
+// Helper to push or concat based on if the 2nd parameter is an array or not
+function pushOrConcat(base, toPush) {
+       if (toPush) {
+               if (helpers.isArray(toPush)) {
+                       // base = base.concat(toPush);
+                       Array.prototype.push.apply(base, toPush);
+               } else {
+                       base.push(toPush);
+               }
+       }
 
-               // Body width
-               ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
-               helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
+       return base;
+}
+
+// Private helper to create a tooltip item model
+// @param element : the chart element (point, arc, bar) to create the tooltip item for
+// @return : new tooltip item
+function createTooltipItem(element) {
+       var xScale = element._xScale;
+       var yScale = element._yScale || element._scale; // handle radar || polarArea charts
+       var index = element._index;
+       var datasetIndex = element._datasetIndex;
+
+       return {
+               xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '',
+               yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '',
+               index: index,
+               datasetIndex: datasetIndex,
+               x: element._model.x,
+               y: element._model.y
+       };
+}
+
+/**
+ * Helper to get the reset model for the tooltip
+ * @param tooltipOpts {Object} the tooltip options
+ */
+function getBaseModel(tooltipOpts) {
+       var globalDefaults = defaults.global;
+       var valueOrDefault = helpers.valueOrDefault;
+
+       return {
+               // Positioning
+               xPadding: tooltipOpts.xPadding,
+               yPadding: tooltipOpts.yPadding,
+               xAlign: tooltipOpts.xAlign,
+               yAlign: tooltipOpts.yAlign,
+
+               // Body
+               bodyFontColor: tooltipOpts.bodyFontColor,
+               _bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
+               _bodyFontStyle: valueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle),
+               _bodyAlign: tooltipOpts.bodyAlign,
+               bodyFontSize: valueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize),
+               bodySpacing: tooltipOpts.bodySpacing,
+
+               // Title
+               titleFontColor: tooltipOpts.titleFontColor,
+               _titleFontFamily: valueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily),
+               _titleFontStyle: valueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle),
+               titleFontSize: valueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize),
+               _titleAlign: tooltipOpts.titleAlign,
+               titleSpacing: tooltipOpts.titleSpacing,
+               titleMarginBottom: tooltipOpts.titleMarginBottom,
+
+               // Footer
+               footerFontColor: tooltipOpts.footerFontColor,
+               _footerFontFamily: valueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily),
+               _footerFontStyle: valueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle),
+               footerFontSize: valueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize),
+               _footerAlign: tooltipOpts.footerAlign,
+               footerSpacing: tooltipOpts.footerSpacing,
+               footerMarginTop: tooltipOpts.footerMarginTop,
+
+               // Appearance
+               caretSize: tooltipOpts.caretSize,
+               cornerRadius: tooltipOpts.cornerRadius,
+               backgroundColor: tooltipOpts.backgroundColor,
+               opacity: 0,
+               legendColorBackground: tooltipOpts.multiKeyBackground,
+               displayColors: tooltipOpts.displayColors,
+               borderColor: tooltipOpts.borderColor,
+               borderWidth: tooltipOpts.borderWidth
+       };
+}
+
+/**
+ * Get the size of the tooltip
+ */
+function getTooltipSize(tooltip, model) {
+       var ctx = tooltip._chart.ctx;
+
+       var height = model.yPadding * 2; // Tooltip Padding
+       var width = 0;
+
+       // Count of all lines in the body
+       var body = model.body;
+       var combinedBodyLength = body.reduce(function(count, bodyItem) {
+               return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
+       }, 0);
+       combinedBodyLength += model.beforeBody.length + model.afterBody.length;
+
+       var titleLineCount = model.title.length;
+       var footerLineCount = model.footer.length;
+       var titleFontSize = model.titleFontSize;
+       var bodyFontSize = model.bodyFontSize;
+       var footerFontSize = model.footerFontSize;
+
+       height += titleLineCount * titleFontSize; // Title Lines
+       height += titleLineCount ? (titleLineCount - 1) * model.titleSpacing : 0; // Title Line Spacing
+       height += titleLineCount ? model.titleMarginBottom : 0; // Title's bottom Margin
+       height += combinedBodyLength * bodyFontSize; // Body Lines
+       height += combinedBodyLength ? (combinedBodyLength - 1) * model.bodySpacing : 0; // Body Line Spacing
+       height += footerLineCount ? model.footerMarginTop : 0; // Footer Margin
+       height += footerLineCount * (footerFontSize); // Footer Lines
+       height += footerLineCount ? (footerLineCount - 1) * model.footerSpacing : 0; // Footer Line Spacing
+
+       // Title width
+       var widthPadding = 0;
+       var maxLineWidth = function(line) {
+               width = Math.max(width, ctx.measureText(line).width + widthPadding);
+       };
 
-               // Body lines may include some extra width due to the color box
-               widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
-               helpers.each(body, function(bodyItem) {
-                       helpers.each(bodyItem.before, maxLineWidth);
-                       helpers.each(bodyItem.lines, maxLineWidth);
-                       helpers.each(bodyItem.after, maxLineWidth);
-               });
+       ctx.font = helpers.fontString(titleFontSize, model._titleFontStyle, model._titleFontFamily);
+       helpers.each(model.title, maxLineWidth);
 
-               // Reset back to 0
-               widthPadding = 0;
+       // Body width
+       ctx.font = helpers.fontString(bodyFontSize, model._bodyFontStyle, model._bodyFontFamily);
+       helpers.each(model.beforeBody.concat(model.afterBody), maxLineWidth);
 
-               // Footer width
-               ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
-               helpers.each(model.footer, maxLineWidth);
+       // Body lines may include some extra width due to the color box
+       widthPadding = model.displayColors ? (bodyFontSize + 2) : 0;
+       helpers.each(body, function(bodyItem) {
+               helpers.each(bodyItem.before, maxLineWidth);
+               helpers.each(bodyItem.lines, maxLineWidth);
+               helpers.each(bodyItem.after, maxLineWidth);
+       });
 
-               // Add padding
-               width += 2 * model.xPadding;
+       // Reset back to 0
+       widthPadding = 0;
 
-               return {
-                       width: width,
-                       height: height
-               };
-       }
+       // Footer width
+       ctx.font = helpers.fontString(footerFontSize, model._footerFontStyle, model._footerFontFamily);
+       helpers.each(model.footer, maxLineWidth);
 
-       /**
-        * Helper to get the alignment of a tooltip given the size
-        */
-       function determineAlignment(tooltip, size) {
-               var model = tooltip._model;
-               var chart = tooltip._chart;
-               var chartArea = tooltip._chart.chartArea;
-               var xAlign = 'center';
-               var yAlign = 'center';
-
-               if (model.y < size.height) {
-                       yAlign = 'top';
-               } else if (model.y > (chart.height - size.height)) {
-                       yAlign = 'bottom';
-               }
+       // Add padding
+       width += 2 * model.xPadding;
 
-               var lf, rf; // functions to determine left, right alignment
-               var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
-               var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
-               var midX = (chartArea.left + chartArea.right) / 2;
-               var midY = (chartArea.top + chartArea.bottom) / 2;
+       return {
+               width: width,
+               height: height
+       };
+}
+
+/**
+ * Helper to get the alignment of a tooltip given the size
+ */
+function determineAlignment(tooltip, size) {
+       var model = tooltip._model;
+       var chart = tooltip._chart;
+       var chartArea = tooltip._chart.chartArea;
+       var xAlign = 'center';
+       var yAlign = 'center';
+
+       if (model.y < size.height) {
+               yAlign = 'top';
+       } else if (model.y > (chart.height - size.height)) {
+               yAlign = 'bottom';
+       }
 
-               if (yAlign === 'center') {
-                       lf = function(x) {
-                               return x <= midX;
-                       };
-                       rf = function(x) {
-                               return x > midX;
-                       };
-               } else {
-                       lf = function(x) {
-                               return x <= (size.width / 2);
-                       };
-                       rf = function(x) {
-                               return x >= (chart.width - (size.width / 2));
-                       };
-               }
+       var lf, rf; // functions to determine left, right alignment
+       var olf, orf; // functions to determine if left/right alignment causes tooltip to go outside chart
+       var yf; // function to get the y alignment if the tooltip goes outside of the left or right edges
+       var midX = (chartArea.left + chartArea.right) / 2;
+       var midY = (chartArea.top + chartArea.bottom) / 2;
 
-               olf = function(x) {
-                       return x + size.width + model.caretSize + model.caretPadding > chart.width;
+       if (yAlign === 'center') {
+               lf = function(x) {
+                       return x <= midX;
                };
-               orf = function(x) {
-                       return x - size.width - model.caretSize - model.caretPadding < 0;
+               rf = function(x) {
+                       return x > midX;
                };
-               yf = function(y) {
-                       return y <= midY ? 'top' : 'bottom';
+       } else {
+               lf = function(x) {
+                       return x <= (size.width / 2);
                };
+               rf = function(x) {
+                       return x >= (chart.width - (size.width / 2));
+               };
+       }
 
-               if (lf(model.x)) {
-                       xAlign = 'left';
+       olf = function(x) {
+               return x + size.width + model.caretSize + model.caretPadding > chart.width;
+       };
+       orf = function(x) {
+               return x - size.width - model.caretSize - model.caretPadding < 0;
+       };
+       yf = function(y) {
+               return y <= midY ? 'top' : 'bottom';
+       };
 
-                       // Is tooltip too wide and goes over the right side of the chart.?
-                       if (olf(model.x)) {
-                               xAlign = 'center';
-                               yAlign = yf(model.y);
-                       }
-               } else if (rf(model.x)) {
-                       xAlign = 'right';
+       if (lf(model.x)) {
+               xAlign = 'left';
 
-                       // Is tooltip too wide and goes outside left edge of canvas?
-                       if (orf(model.x)) {
-                               xAlign = 'center';
-                               yAlign = yf(model.y);
-                       }
+               // Is tooltip too wide and goes over the right side of the chart.?
+               if (olf(model.x)) {
+                       xAlign = 'center';
+                       yAlign = yf(model.y);
                }
+       } else if (rf(model.x)) {
+               xAlign = 'right';
 
-               var opts = tooltip._options;
-               return {
-                       xAlign: opts.xAlign ? opts.xAlign : xAlign,
-                       yAlign: opts.yAlign ? opts.yAlign : yAlign
-               };
+               // Is tooltip too wide and goes outside left edge of canvas?
+               if (orf(model.x)) {
+                       xAlign = 'center';
+                       yAlign = yf(model.y);
+               }
        }
 
-       /**
-        * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
-        */
-       function getBackgroundPoint(vm, size, alignment, chart) {
-               // Background Position
-               var x = vm.x;
-               var y = vm.y;
-
-               var caretSize = vm.caretSize;
-               var caretPadding = vm.caretPadding;
-               var cornerRadius = vm.cornerRadius;
-               var xAlign = alignment.xAlign;
-               var yAlign = alignment.yAlign;
-               var paddingAndSize = caretSize + caretPadding;
-               var radiusAndPadding = cornerRadius + caretPadding;
-
-               if (xAlign === 'right') {
-                       x -= size.width;
-               } else if (xAlign === 'center') {
-                       x -= (size.width / 2);
-                       if (x + size.width > chart.width) {
-                               x = chart.width - size.width;
-                       }
-                       if (x < 0) {
-                               x = 0;
-                       }
+       var opts = tooltip._options;
+       return {
+               xAlign: opts.xAlign ? opts.xAlign : xAlign,
+               yAlign: opts.yAlign ? opts.yAlign : yAlign
+       };
+}
+
+/**
+ * @Helper to get the location a tooltip needs to be placed at given the initial position (via the vm) and the size and alignment
+ */
+function getBackgroundPoint(vm, size, alignment, chart) {
+       // Background Position
+       var x = vm.x;
+       var y = vm.y;
+
+       var caretSize = vm.caretSize;
+       var caretPadding = vm.caretPadding;
+       var cornerRadius = vm.cornerRadius;
+       var xAlign = alignment.xAlign;
+       var yAlign = alignment.yAlign;
+       var paddingAndSize = caretSize + caretPadding;
+       var radiusAndPadding = cornerRadius + caretPadding;
+
+       if (xAlign === 'right') {
+               x -= size.width;
+       } else if (xAlign === 'center') {
+               x -= (size.width / 2);
+               if (x + size.width > chart.width) {
+                       x = chart.width - size.width;
                }
-
-               if (yAlign === 'top') {
-                       y += paddingAndSize;
-               } else if (yAlign === 'bottom') {
-                       y -= size.height + paddingAndSize;
-               } else {
-                       y -= (size.height / 2);
+               if (x < 0) {
+                       x = 0;
                }
+       }
 
-               if (yAlign === 'center') {
-                       if (xAlign === 'left') {
-                               x += paddingAndSize;
-                       } else if (xAlign === 'right') {
-                               x -= paddingAndSize;
-                       }
-               } else if (xAlign === 'left') {
-                       x -= radiusAndPadding;
+       if (yAlign === 'top') {
+               y += paddingAndSize;
+       } else if (yAlign === 'bottom') {
+               y -= size.height + paddingAndSize;
+       } else {
+               y -= (size.height / 2);
+       }
+
+       if (yAlign === 'center') {
+               if (xAlign === 'left') {
+                       x += paddingAndSize;
                } else if (xAlign === 'right') {
-                       x += radiusAndPadding;
+                       x -= paddingAndSize;
                }
-
-               return {
-                       x: x,
-                       y: y
-               };
+       } else if (xAlign === 'left') {
+               x -= radiusAndPadding;
+       } else if (xAlign === 'right') {
+               x += radiusAndPadding;
        }
 
-       Chart.Tooltip = Element.extend({
-               initialize: function() {
-                       this._model = getBaseModel(this._options);
-                       this._lastActive = [];
-               },
-
-               // Get the title
-               // Args are: (tooltipItem, data)
-               getTitle: function() {
-                       var me = this;
-                       var opts = me._options;
-                       var callbacks = opts.callbacks;
-
-                       var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
-                       var title = callbacks.title.apply(me, arguments);
-                       var afterTitle = callbacks.afterTitle.apply(me, arguments);
-
-                       var lines = [];
-                       lines = pushOrConcat(lines, beforeTitle);
-                       lines = pushOrConcat(lines, title);
-                       lines = pushOrConcat(lines, afterTitle);
-
-                       return lines;
-               },
-
-               // Args are: (tooltipItem, data)
-               getBeforeBody: function() {
-                       var lines = this._options.callbacks.beforeBody.apply(this, arguments);
-                       return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
-               },
-
-               // Args are: (tooltipItem, data)
-               getBody: function(tooltipItems, data) {
-                       var me = this;
-                       var callbacks = me._options.callbacks;
-                       var bodyItems = [];
+       return {
+               x: x,
+               y: y
+       };
+}
+
+var exports = module.exports = Element.extend({
+       initialize: function() {
+               this._model = getBaseModel(this._options);
+               this._lastActive = [];
+       },
+
+       // Get the title
+       // Args are: (tooltipItem, data)
+       getTitle: function() {
+               var me = this;
+               var opts = me._options;
+               var callbacks = opts.callbacks;
+
+               var beforeTitle = callbacks.beforeTitle.apply(me, arguments);
+               var title = callbacks.title.apply(me, arguments);
+               var afterTitle = callbacks.afterTitle.apply(me, arguments);
+
+               var lines = [];
+               lines = pushOrConcat(lines, beforeTitle);
+               lines = pushOrConcat(lines, title);
+               lines = pushOrConcat(lines, afterTitle);
+
+               return lines;
+       },
+
+       // Args are: (tooltipItem, data)
+       getBeforeBody: function() {
+               var lines = this._options.callbacks.beforeBody.apply(this, arguments);
+               return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
+       },
+
+       // Args are: (tooltipItem, data)
+       getBody: function(tooltipItems, data) {
+               var me = this;
+               var callbacks = me._options.callbacks;
+               var bodyItems = [];
+
+               helpers.each(tooltipItems, function(tooltipItem) {
+                       var bodyItem = {
+                               before: [],
+                               lines: [],
+                               after: []
+                       };
+                       pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
+                       pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
+                       pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
 
-                       helpers.each(tooltipItems, function(tooltipItem) {
-                               var bodyItem = {
-                                       before: [],
-                                       lines: [],
-                                       after: []
-                               };
-                               pushOrConcat(bodyItem.before, callbacks.beforeLabel.call(me, tooltipItem, data));
-                               pushOrConcat(bodyItem.lines, callbacks.label.call(me, tooltipItem, data));
-                               pushOrConcat(bodyItem.after, callbacks.afterLabel.call(me, tooltipItem, data));
+                       bodyItems.push(bodyItem);
+               });
 
-                               bodyItems.push(bodyItem);
-                       });
+               return bodyItems;
+       },
+
+       // Args are: (tooltipItem, data)
+       getAfterBody: function() {
+               var lines = this._options.callbacks.afterBody.apply(this, arguments);
+               return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
+       },
+
+       // Get the footer and beforeFooter and afterFooter lines
+       // Args are: (tooltipItem, data)
+       getFooter: function() {
+               var me = this;
+               var callbacks = me._options.callbacks;
+
+               var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
+               var footer = callbacks.footer.apply(me, arguments);
+               var afterFooter = callbacks.afterFooter.apply(me, arguments);
+
+               var lines = [];
+               lines = pushOrConcat(lines, beforeFooter);
+               lines = pushOrConcat(lines, footer);
+               lines = pushOrConcat(lines, afterFooter);
+
+               return lines;
+       },
+
+       update: function(changed) {
+               var me = this;
+               var opts = me._options;
+
+               // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
+               // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time
+               // which breaks any animations.
+               var existingModel = me._model;
+               var model = me._model = getBaseModel(opts);
+               var active = me._active;
+
+               var data = me._data;
+
+               // In the case where active.length === 0 we need to keep these at existing values for good animations
+               var alignment = {
+                       xAlign: existingModel.xAlign,
+                       yAlign: existingModel.yAlign
+               };
+               var backgroundPoint = {
+                       x: existingModel.x,
+                       y: existingModel.y
+               };
+               var tooltipSize = {
+                       width: existingModel.width,
+                       height: existingModel.height
+               };
+               var tooltipPosition = {
+                       x: existingModel.caretX,
+                       y: existingModel.caretY
+               };
 
-                       return bodyItems;
-               },
-
-               // Args are: (tooltipItem, data)
-               getAfterBody: function() {
-                       var lines = this._options.callbacks.afterBody.apply(this, arguments);
-                       return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
-               },
-
-               // Get the footer and beforeFooter and afterFooter lines
-               // Args are: (tooltipItem, data)
-               getFooter: function() {
-                       var me = this;
-                       var callbacks = me._options.callbacks;
-
-                       var beforeFooter = callbacks.beforeFooter.apply(me, arguments);
-                       var footer = callbacks.footer.apply(me, arguments);
-                       var afterFooter = callbacks.afterFooter.apply(me, arguments);
-
-                       var lines = [];
-                       lines = pushOrConcat(lines, beforeFooter);
-                       lines = pushOrConcat(lines, footer);
-                       lines = pushOrConcat(lines, afterFooter);
-
-                       return lines;
-               },
-
-               update: function(changed) {
-                       var me = this;
-                       var opts = me._options;
-
-                       // Need to regenerate the model because its faster than using extend and it is necessary due to the optimization in Chart.Element.transition
-                       // that does _view = _model if ease === 1. This causes the 2nd tooltip update to set properties in both the view and model at the same time
-                       // which breaks any animations.
-                       var existingModel = me._model;
-                       var model = me._model = getBaseModel(opts);
-                       var active = me._active;
-
-                       var data = me._data;
-
-                       // In the case where active.length === 0 we need to keep these at existing values for good animations
-                       var alignment = {
-                               xAlign: existingModel.xAlign,
-                               yAlign: existingModel.yAlign
-                       };
-                       var backgroundPoint = {
-                               x: existingModel.x,
-                               y: existingModel.y
-                       };
-                       var tooltipSize = {
-                               width: existingModel.width,
-                               height: existingModel.height
-                       };
-                       var tooltipPosition = {
-                               x: existingModel.caretX,
-                               y: existingModel.caretY
-                       };
+               var i, len;
 
-                       var i, len;
+               if (active.length) {
+                       model.opacity = 1;
 
-                       if (active.length) {
-                               model.opacity = 1;
+                       var labelColors = [];
+                       var labelTextColors = [];
+                       tooltipPosition = positioners[opts.position].call(me, active, me._eventPosition);
 
-                               var labelColors = [];
-                               var labelTextColors = [];
-                               tooltipPosition = Chart.Tooltip.positioners[opts.position].call(me, active, me._eventPosition);
+                       var tooltipItems = [];
+                       for (i = 0, len = active.length; i < len; ++i) {
+                               tooltipItems.push(createTooltipItem(active[i]));
+                       }
 
-                               var tooltipItems = [];
-                               for (i = 0, len = active.length; i < len; ++i) {
-                                       tooltipItems.push(createTooltipItem(active[i]));
-                               }
+                       // If the user provided a filter function, use it to modify the tooltip items
+                       if (opts.filter) {
+                               tooltipItems = tooltipItems.filter(function(a) {
+                                       return opts.filter(a, data);
+                               });
+                       }
 
-                               // If the user provided a filter function, use it to modify the tooltip items
-                               if (opts.filter) {
-                                       tooltipItems = tooltipItems.filter(function(a) {
-                                               return opts.filter(a, data);
-                                       });
-                               }
+                       // If the user provided a sorting function, use it to modify the tooltip items
+                       if (opts.itemSort) {
+                               tooltipItems = tooltipItems.sort(function(a, b) {
+                                       return opts.itemSort(a, b, data);
+                               });
+                       }
 
-                               // If the user provided a sorting function, use it to modify the tooltip items
-                               if (opts.itemSort) {
-                                       tooltipItems = tooltipItems.sort(function(a, b) {
-                                               return opts.itemSort(a, b, data);
-                                       });
-                               }
+                       // Determine colors for boxes
+                       helpers.each(tooltipItems, function(tooltipItem) {
+                               labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
+                               labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
+                       });
 
-                               // Determine colors for boxes
-                               helpers.each(tooltipItems, function(tooltipItem) {
-                                       labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, me._chart));
-                                       labelTextColors.push(opts.callbacks.labelTextColor.call(me, tooltipItem, me._chart));
-                               });
 
+                       // Build the Text Lines
+                       model.title = me.getTitle(tooltipItems, data);
+                       model.beforeBody = me.getBeforeBody(tooltipItems, data);
+                       model.body = me.getBody(tooltipItems, data);
+                       model.afterBody = me.getAfterBody(tooltipItems, data);
+                       model.footer = me.getFooter(tooltipItems, data);
+
+                       // Initial positioning and colors
+                       model.x = Math.round(tooltipPosition.x);
+                       model.y = Math.round(tooltipPosition.y);
+                       model.caretPadding = opts.caretPadding;
+                       model.labelColors = labelColors;
+                       model.labelTextColors = labelTextColors;
+
+                       // data points
+                       model.dataPoints = tooltipItems;
+
+                       // We need to determine alignment of the tooltip
+                       tooltipSize = getTooltipSize(this, model);
+                       alignment = determineAlignment(this, tooltipSize);
+                       // Final Size and Position
+                       backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
+               } else {
+                       model.opacity = 0;
+               }
 
-                               // Build the Text Lines
-                               model.title = me.getTitle(tooltipItems, data);
-                               model.beforeBody = me.getBeforeBody(tooltipItems, data);
-                               model.body = me.getBody(tooltipItems, data);
-                               model.afterBody = me.getAfterBody(tooltipItems, data);
-                               model.footer = me.getFooter(tooltipItems, data);
-
-                               // Initial positioning and colors
-                               model.x = Math.round(tooltipPosition.x);
-                               model.y = Math.round(tooltipPosition.y);
-                               model.caretPadding = opts.caretPadding;
-                               model.labelColors = labelColors;
-                               model.labelTextColors = labelTextColors;
-
-                               // data points
-                               model.dataPoints = tooltipItems;
-
-                               // We need to determine alignment of the tooltip
-                               tooltipSize = getTooltipSize(this, model);
-                               alignment = determineAlignment(this, tooltipSize);
-                               // Final Size and Position
-                               backgroundPoint = getBackgroundPoint(model, tooltipSize, alignment, me._chart);
-                       } else {
-                               model.opacity = 0;
-                       }
+               model.xAlign = alignment.xAlign;
+               model.yAlign = alignment.yAlign;
+               model.x = backgroundPoint.x;
+               model.y = backgroundPoint.y;
+               model.width = tooltipSize.width;
+               model.height = tooltipSize.height;
 
-                       model.xAlign = alignment.xAlign;
-                       model.yAlign = alignment.yAlign;
-                       model.x = backgroundPoint.x;
-                       model.y = backgroundPoint.y;
-                       model.width = tooltipSize.width;
-                       model.height = tooltipSize.height;
+               // Point where the caret on the tooltip points to
+               model.caretX = tooltipPosition.x;
+               model.caretY = tooltipPosition.y;
 
-                       // Point where the caret on the tooltip points to
-                       model.caretX = tooltipPosition.x;
-                       model.caretY = tooltipPosition.y;
+               me._model = model;
 
-                       me._model = model;
+               if (changed && opts.custom) {
+                       opts.custom.call(me, model);
+               }
 
-                       if (changed && opts.custom) {
-                               opts.custom.call(me, model);
-                       }
+               return me;
+       },
 
-                       return me;
-               },
-               drawCaret: function(tooltipPoint, size) {
-                       var ctx = this._chart.ctx;
-                       var vm = this._view;
-                       var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
-
-                       ctx.lineTo(caretPosition.x1, caretPosition.y1);
-                       ctx.lineTo(caretPosition.x2, caretPosition.y2);
-                       ctx.lineTo(caretPosition.x3, caretPosition.y3);
-               },
-               getCaretPosition: function(tooltipPoint, size, vm) {
-                       var x1, x2, x3, y1, y2, y3;
-                       var caretSize = vm.caretSize;
-                       var cornerRadius = vm.cornerRadius;
-                       var xAlign = vm.xAlign;
-                       var yAlign = vm.yAlign;
-                       var ptX = tooltipPoint.x;
-                       var ptY = tooltipPoint.y;
-                       var width = size.width;
-                       var height = size.height;
-
-                       if (yAlign === 'center') {
-                               y2 = ptY + (height / 2);
-
-                               if (xAlign === 'left') {
-                                       x1 = ptX;
-                                       x2 = x1 - caretSize;
-                                       x3 = x1;
-
-                                       y1 = y2 + caretSize;
-                                       y3 = y2 - caretSize;
-                               } else {
-                                       x1 = ptX + width;
-                                       x2 = x1 + caretSize;
-                                       x3 = x1;
-
-                                       y1 = y2 - caretSize;
-                                       y3 = y2 + caretSize;
-                               }
-                       } else {
-                               if (xAlign === 'left') {
-                                       x2 = ptX + cornerRadius + (caretSize);
-                                       x1 = x2 - caretSize;
-                                       x3 = x2 + caretSize;
-                               } else if (xAlign === 'right') {
-                                       x2 = ptX + width - cornerRadius - caretSize;
-                                       x1 = x2 - caretSize;
-                                       x3 = x2 + caretSize;
-                               } else {
-                                       x2 = vm.caretX;
-                                       x1 = x2 - caretSize;
-                                       x3 = x2 + caretSize;
-                               }
-                               if (yAlign === 'top') {
-                                       y1 = ptY;
-                                       y2 = y1 - caretSize;
-                                       y3 = y1;
-                               } else {
-                                       y1 = ptY + height;
-                                       y2 = y1 + caretSize;
-                                       y3 = y1;
-                                       // invert drawing order
-                                       var tmp = x3;
-                                       x3 = x1;
-                                       x1 = tmp;
-                               }
-                       }
-                       return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
-               },
-               drawTitle: function(pt, vm, ctx, opacity) {
-                       var title = vm.title;
+       drawCaret: function(tooltipPoint, size) {
+               var ctx = this._chart.ctx;
+               var vm = this._view;
+               var caretPosition = this.getCaretPosition(tooltipPoint, size, vm);
 
-                       if (title.length) {
-                               ctx.textAlign = vm._titleAlign;
-                               ctx.textBaseline = 'top';
+               ctx.lineTo(caretPosition.x1, caretPosition.y1);
+               ctx.lineTo(caretPosition.x2, caretPosition.y2);
+               ctx.lineTo(caretPosition.x3, caretPosition.y3);
+       },
+       getCaretPosition: function(tooltipPoint, size, vm) {
+               var x1, x2, x3, y1, y2, y3;
+               var caretSize = vm.caretSize;
+               var cornerRadius = vm.cornerRadius;
+               var xAlign = vm.xAlign;
+               var yAlign = vm.yAlign;
+               var ptX = tooltipPoint.x;
+               var ptY = tooltipPoint.y;
+               var width = size.width;
+               var height = size.height;
 
-                               var titleFontSize = vm.titleFontSize;
-                               var titleSpacing = vm.titleSpacing;
+               if (yAlign === 'center') {
+                       y2 = ptY + (height / 2);
 
-                               ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
-                               ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
+                       if (xAlign === 'left') {
+                               x1 = ptX;
+                               x2 = x1 - caretSize;
+                               x3 = x1;
 
-                               var i, len;
-                               for (i = 0, len = title.length; i < len; ++i) {
-                                       ctx.fillText(title[i], pt.x, pt.y);
-                                       pt.y += titleFontSize + titleSpacing; // Line Height and spacing
+                               y1 = y2 + caretSize;
+                               y3 = y2 - caretSize;
+                       } else {
+                               x1 = ptX + width;
+                               x2 = x1 + caretSize;
+                               x3 = x1;
 
-                                       if (i + 1 === title.length) {
-                                               pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
-                                       }
-                               }
+                               y1 = y2 - caretSize;
+                               y3 = y2 + caretSize;
+                       }
+               } else {
+                       if (xAlign === 'left') {
+                               x2 = ptX + cornerRadius + (caretSize);
+                               x1 = x2 - caretSize;
+                               x3 = x2 + caretSize;
+                       } else if (xAlign === 'right') {
+                               x2 = ptX + width - cornerRadius - caretSize;
+                               x1 = x2 - caretSize;
+                               x3 = x2 + caretSize;
+                       } else {
+                               x2 = vm.caretX;
+                               x1 = x2 - caretSize;
+                               x3 = x2 + caretSize;
+                       }
+                       if (yAlign === 'top') {
+                               y1 = ptY;
+                               y2 = y1 - caretSize;
+                               y3 = y1;
+                       } else {
+                               y1 = ptY + height;
+                               y2 = y1 + caretSize;
+                               y3 = y1;
+                               // invert drawing order
+                               var tmp = x3;
+                               x3 = x1;
+                               x1 = tmp;
                        }
-               },
-               drawBody: function(pt, vm, ctx, opacity) {
-                       var bodyFontSize = vm.bodyFontSize;
-                       var bodySpacing = vm.bodySpacing;
-                       var body = vm.body;
+               }
+               return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3};
+       },
+
+       drawTitle: function(pt, vm, ctx, opacity) {
+               var title = vm.title;
 
-                       ctx.textAlign = vm._bodyAlign;
+               if (title.length) {
+                       ctx.textAlign = vm._titleAlign;
                        ctx.textBaseline = 'top';
-                       ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
 
-                       // Before Body
-                       var xLinePadding = 0;
-                       var fillLineOfText = function(line) {
-                               ctx.fillText(line, pt.x + xLinePadding, pt.y);
-                               pt.y += bodyFontSize + bodySpacing;
-                       };
+                       var titleFontSize = vm.titleFontSize;
+                       var titleSpacing = vm.titleSpacing;
 
-                       // Before body lines
-                       ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
-                       helpers.each(vm.beforeBody, fillLineOfText);
-
-                       var drawColorBoxes = vm.displayColors;
-                       xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
-
-                       // Draw body lines now
-                       helpers.each(body, function(bodyItem, i) {
-                               var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
-                               ctx.fillStyle = textColor;
-                               helpers.each(bodyItem.before, fillLineOfText);
-
-                               helpers.each(bodyItem.lines, function(line) {
-                                       // Draw Legend-like boxes if needed
-                                       if (drawColorBoxes) {
-                                               // Fill a white rect so that colours merge nicely if the opacity is < 1
-                                               ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
-                                               ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
-
-                                               // Border
-                                               ctx.lineWidth = 1;
-                                               ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
-                                               ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
-
-                                               // Inner square
-                                               ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
-                                               ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
-                                               ctx.fillStyle = textColor;
-                                       }
+                       ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity);
+                       ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
 
-                                       fillLineOfText(line);
-                               });
+                       var i, len;
+                       for (i = 0, len = title.length; i < len; ++i) {
+                               ctx.fillText(title[i], pt.x, pt.y);
+                               pt.y += titleFontSize + titleSpacing; // Line Height and spacing
 
-                               helpers.each(bodyItem.after, fillLineOfText);
-                       });
+                               if (i + 1 === title.length) {
+                                       pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing
+                               }
+                       }
+               }
+       },
+
+       drawBody: function(pt, vm, ctx, opacity) {
+               var bodyFontSize = vm.bodyFontSize;
+               var bodySpacing = vm.bodySpacing;
+               var body = vm.body;
+
+               ctx.textAlign = vm._bodyAlign;
+               ctx.textBaseline = 'top';
+               ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
+
+               // Before Body
+               var xLinePadding = 0;
+               var fillLineOfText = function(line) {
+                       ctx.fillText(line, pt.x + xLinePadding, pt.y);
+                       pt.y += bodyFontSize + bodySpacing;
+               };
 
-                       // Reset back to 0 for after body
-                       xLinePadding = 0;
+               // Before body lines
+               ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity);
+               helpers.each(vm.beforeBody, fillLineOfText);
+
+               var drawColorBoxes = vm.displayColors;
+               xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
+
+               // Draw body lines now
+               helpers.each(body, function(bodyItem, i) {
+                       var textColor = mergeOpacity(vm.labelTextColors[i], opacity);
+                       ctx.fillStyle = textColor;
+                       helpers.each(bodyItem.before, fillLineOfText);
+
+                       helpers.each(bodyItem.lines, function(line) {
+                               // Draw Legend-like boxes if needed
+                               if (drawColorBoxes) {
+                                       // Fill a white rect so that colours merge nicely if the opacity is < 1
+                                       ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity);
+                                       ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
+
+                                       // Border
+                                       ctx.lineWidth = 1;
+                                       ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity);
+                                       ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
+
+                                       // Inner square
+                                       ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity);
+                                       ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
+                                       ctx.fillStyle = textColor;
+                               }
 
-                       // After body lines
-                       helpers.each(vm.afterBody, fillLineOfText);
-                       pt.y -= bodySpacing; // Remove last body spacing
-               },
-               drawFooter: function(pt, vm, ctx, opacity) {
-                       var footer = vm.footer;
+                               fillLineOfText(line);
+                       });
 
-                       if (footer.length) {
-                               pt.y += vm.footerMarginTop;
+                       helpers.each(bodyItem.after, fillLineOfText);
+               });
 
-                               ctx.textAlign = vm._footerAlign;
-                               ctx.textBaseline = 'top';
+               // Reset back to 0 for after body
+               xLinePadding = 0;
 
-                               ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
-                               ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
+               // After body lines
+               helpers.each(vm.afterBody, fillLineOfText);
+               pt.y -= bodySpacing; // Remove last body spacing
+       },
 
-                               helpers.each(footer, function(line) {
-                                       ctx.fillText(line, pt.x, pt.y);
-                                       pt.y += vm.footerFontSize + vm.footerSpacing;
-                               });
-                       }
-               },
-               drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
-                       ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
-                       ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
-                       ctx.lineWidth = vm.borderWidth;
-                       var xAlign = vm.xAlign;
-                       var yAlign = vm.yAlign;
-                       var x = pt.x;
-                       var y = pt.y;
-                       var width = tooltipSize.width;
-                       var height = tooltipSize.height;
-                       var radius = vm.cornerRadius;
-
-                       ctx.beginPath();
-                       ctx.moveTo(x + radius, y);
-                       if (yAlign === 'top') {
-                               this.drawCaret(pt, tooltipSize);
-                       }
-                       ctx.lineTo(x + width - radius, y);
-                       ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
-                       if (yAlign === 'center' && xAlign === 'right') {
-                               this.drawCaret(pt, tooltipSize);
-                       }
-                       ctx.lineTo(x + width, y + height - radius);
-                       ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
-                       if (yAlign === 'bottom') {
-                               this.drawCaret(pt, tooltipSize);
-                       }
-                       ctx.lineTo(x + radius, y + height);
-                       ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
-                       if (yAlign === 'center' && xAlign === 'left') {
-                               this.drawCaret(pt, tooltipSize);
-                       }
-                       ctx.lineTo(x, y + radius);
-                       ctx.quadraticCurveTo(x, y, x + radius, y);
-                       ctx.closePath();
+       drawFooter: function(pt, vm, ctx, opacity) {
+               var footer = vm.footer;
 
-                       ctx.fill();
+               if (footer.length) {
+                       pt.y += vm.footerMarginTop;
 
-                       if (vm.borderWidth > 0) {
-                               ctx.stroke();
-                       }
-               },
-               draw: function() {
-                       var ctx = this._chart.ctx;
-                       var vm = this._view;
+                       ctx.textAlign = vm._footerAlign;
+                       ctx.textBaseline = 'top';
 
-                       if (vm.opacity === 0) {
-                               return;
-                       }
+                       ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity);
+                       ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
 
-                       var tooltipSize = {
-                               width: vm.width,
-                               height: vm.height
-                       };
-                       var pt = {
-                               x: vm.x,
-                               y: vm.y
-                       };
+                       helpers.each(footer, function(line) {
+                               ctx.fillText(line, pt.x, pt.y);
+                               pt.y += vm.footerFontSize + vm.footerSpacing;
+                       });
+               }
+       },
+
+       drawBackground: function(pt, vm, ctx, tooltipSize, opacity) {
+               ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity);
+               ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity);
+               ctx.lineWidth = vm.borderWidth;
+               var xAlign = vm.xAlign;
+               var yAlign = vm.yAlign;
+               var x = pt.x;
+               var y = pt.y;
+               var width = tooltipSize.width;
+               var height = tooltipSize.height;
+               var radius = vm.cornerRadius;
+
+               ctx.beginPath();
+               ctx.moveTo(x + radius, y);
+               if (yAlign === 'top') {
+                       this.drawCaret(pt, tooltipSize);
+               }
+               ctx.lineTo(x + width - radius, y);
+               ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
+               if (yAlign === 'center' && xAlign === 'right') {
+                       this.drawCaret(pt, tooltipSize);
+               }
+               ctx.lineTo(x + width, y + height - radius);
+               ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
+               if (yAlign === 'bottom') {
+                       this.drawCaret(pt, tooltipSize);
+               }
+               ctx.lineTo(x + radius, y + height);
+               ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
+               if (yAlign === 'center' && xAlign === 'left') {
+                       this.drawCaret(pt, tooltipSize);
+               }
+               ctx.lineTo(x, y + radius);
+               ctx.quadraticCurveTo(x, y, x + radius, y);
+               ctx.closePath();
 
-                       // IE11/Edge does not like very small opacities, so snap to 0
-                       var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
+               ctx.fill();
 
-                       // Truthy/falsey value for empty tooltip
-                       var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
+               if (vm.borderWidth > 0) {
+                       ctx.stroke();
+               }
+       },
 
-                       if (this._options.enabled && hasTooltipContent) {
-                               // Draw Background
-                               this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
+       draw: function() {
+               var ctx = this._chart.ctx;
+               var vm = this._view;
 
-                               // Draw Title, Body, and Footer
-                               pt.x += vm.xPadding;
-                               pt.y += vm.yPadding;
+               if (vm.opacity === 0) {
+                       return;
+               }
 
-                               // Titles
-                               this.drawTitle(pt, vm, ctx, opacity);
+               var tooltipSize = {
+                       width: vm.width,
+                       height: vm.height
+               };
+               var pt = {
+                       x: vm.x,
+                       y: vm.y
+               };
 
-                               // Body
-                               this.drawBody(pt, vm, ctx, opacity);
+               // IE11/Edge does not like very small opacities, so snap to 0
+               var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
 
-                               // Footer
-                               this.drawFooter(pt, vm, ctx, opacity);
-                       }
-               },
-
-               /**
-                * Handle an event
-                * @private
-                * @param {IEvent} event - The event to handle
-                * @returns {Boolean} true if the tooltip changed
-                */
-               handleEvent: function(e) {
-                       var me = this;
-                       var options = me._options;
-                       var changed = false;
-
-                       me._lastActive = me._lastActive || [];
-
-                       // Find Active Elements for tooltips
-                       if (e.type === 'mouseout') {
-                               me._active = [];
-                       } else {
-                               me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
-                       }
+               // Truthy/falsey value for empty tooltip
+               var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length;
 
-                       // Remember Last Actives
-                       changed = !helpers.arrayEquals(me._active, me._lastActive);
+               if (this._options.enabled && hasTooltipContent) {
+                       // Draw Background
+                       this.drawBackground(pt, vm, ctx, tooltipSize, opacity);
 
-                       // Only handle target event on tooltip change
-                       if (changed) {
-                               me._lastActive = me._active;
+                       // Draw Title, Body, and Footer
+                       pt.x += vm.xPadding;
+                       pt.y += vm.yPadding;
 
-                               if (options.enabled || options.custom) {
-                                       me._eventPosition = {
-                                               x: e.x,
-                                               y: e.y
-                                       };
+                       // Titles
+                       this.drawTitle(pt, vm, ctx, opacity);
 
-                                       me.update(true);
-                                       me.pivot();
-                               }
-                       }
+                       // Body
+                       this.drawBody(pt, vm, ctx, opacity);
 
-                       return changed;
+                       // Footer
+                       this.drawFooter(pt, vm, ctx, opacity);
                }
-       });
+       },
 
        /**
-        * @namespace Chart.Tooltip.positioners
+        * Handle an event
+        * @private
+        * @param {IEvent} event - The event to handle
+        * @returns {Boolean} true if the tooltip changed
         */
-       Chart.Tooltip.positioners = {
-               /**
-                * Average mode places the tooltip at the average position of the elements shown
-                * @function Chart.Tooltip.positioners.average
-                * @param elements {ChartElement[]} the elements being displayed in the tooltip
-                * @returns {Point} tooltip position
-                */
-               average: function(elements) {
-                       if (!elements.length) {
-                               return false;
-                       }
+       handleEvent: function(e) {
+               var me = this;
+               var options = me._options;
+               var changed = false;
 
-                       var i, len;
-                       var x = 0;
-                       var y = 0;
-                       var count = 0;
-
-                       for (i = 0, len = elements.length; i < len; ++i) {
-                               var el = elements[i];
-                               if (el && el.hasValue()) {
-                                       var pos = el.tooltipPosition();
-                                       x += pos.x;
-                                       y += pos.y;
-                                       ++count;
-                               }
-                       }
+               me._lastActive = me._lastActive || [];
 
-                       return {
-                               x: Math.round(x / count),
-                               y: Math.round(y / count)
-                       };
-               },
-
-               /**
-                * Gets the tooltip position nearest of the item nearest to the event position
-                * @function Chart.Tooltip.positioners.nearest
-                * @param elements {Chart.Element[]} the tooltip elements
-                * @param eventPosition {Point} the position of the event in canvas coordinates
-                * @returns {Point} the tooltip position
-                */
-               nearest: function(elements, eventPosition) {
-                       var x = eventPosition.x;
-                       var y = eventPosition.y;
-                       var minDistance = Number.POSITIVE_INFINITY;
-                       var i, len, nearestElement;
-
-                       for (i = 0, len = elements.length; i < len; ++i) {
-                               var el = elements[i];
-                               if (el && el.hasValue()) {
-                                       var center = el.getCenterPoint();
-                                       var d = helpers.distanceBetweenPoints(eventPosition, center);
-
-                                       if (d < minDistance) {
-                                               minDistance = d;
-                                               nearestElement = el;
-                                       }
-                               }
-                       }
+               // Find Active Elements for tooltips
+               if (e.type === 'mouseout') {
+                       me._active = [];
+               } else {
+                       me._active = me._chart.getElementsAtEventForMode(e, options.mode, options);
+               }
 
-                       if (nearestElement) {
-                               var tp = nearestElement.tooltipPosition();
-                               x = tp.x;
-                               y = tp.y;
-                       }
+               // Remember Last Actives
+               changed = !helpers.arrayEquals(me._active, me._lastActive);
 
-                       return {
-                               x: x,
-                               y: y
-                       };
+               // Only handle target event on tooltip change
+               if (changed) {
+                       me._lastActive = me._active;
+
+                       if (options.enabled || options.custom) {
+                               me._eventPosition = {
+                                       x: e.x,
+                                       y: e.y
+                               };
+
+                               me.update(true);
+                               me.pivot();
+                       }
                }
-       };
-};
+
+               return changed;
+       }
+});
+
+/**
+ * @namespace Chart.Tooltip.positioners
+ */
+exports.positioners = positioners;
+
index 0e859901434db3c4a17a4416df6996a431fad3b2..a01284c1a0b3060321a3703f81a67ae0bcace0bb 100644 (file)
@@ -1,4 +1,3 @@
-// Test the bubble chart default config
 describe('Default Configs', function() {
        describe('Bubble Chart', function() {
                it('should return correct tooltip strings', function() {
diff --git a/test/specs/global.namespace.tests.js b/test/specs/global.namespace.tests.js
new file mode 100644 (file)
index 0000000..4126df5
--- /dev/null
@@ -0,0 +1,44 @@
+describe('Chart namespace', function() {
+       describe('Chart', function() {
+               it('should a function (constructor)', function() {
+                       expect(Chart instanceof Function).toBeTruthy();
+               });
+               it('should define "core" properties', function() {
+                       expect(Chart instanceof Function).toBeTruthy();
+                       expect(Chart.Animation instanceof Object).toBeTruthy();
+                       expect(Chart.animationService instanceof Object).toBeTruthy();
+                       expect(Chart.defaults instanceof Object).toBeTruthy();
+                       expect(Chart.Element instanceof Object).toBeTruthy();
+                       expect(Chart.Interaction instanceof Object).toBeTruthy();
+                       expect(Chart.layouts instanceof Object).toBeTruthy();
+                       expect(Chart.plugins instanceof Object).toBeTruthy();
+                       expect(Chart.platform instanceof Object).toBeTruthy();
+                       expect(Chart.Ticks instanceof Object).toBeTruthy();
+                       expect(Chart.Tooltip instanceof Object).toBeTruthy();
+                       expect(Chart.Tooltip.positioners instanceof Object).toBeTruthy();
+               });
+       });
+
+       describe('Chart.elements', function() {
+               it('should be an object', function() {
+                       expect(Chart.elements instanceof Object).toBeTruthy();
+               });
+               it('should contains "elements" classes', function() {
+                       expect(Chart.elements.Arc instanceof Function).toBeTruthy();
+                       expect(Chart.elements.Line instanceof Function).toBeTruthy();
+                       expect(Chart.elements.Point instanceof Function).toBeTruthy();
+                       expect(Chart.elements.Rectangle instanceof Function).toBeTruthy();
+               });
+       });
+
+       describe('Chart.helpers', function() {
+               it('should be an object', function() {
+                       expect(Chart.helpers instanceof Object).toBeTruthy();
+               });
+               it('should contains "helpers" namespaces', function() {
+                       expect(Chart.helpers.easing instanceof Object).toBeTruthy();
+                       expect(Chart.helpers.canvas instanceof Object).toBeTruthy();
+                       expect(Chart.helpers.options instanceof Object).toBeTruthy();
+               });
+       });
+});