}
});
-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;
+