From: Evert Timberg Date: Sat, 28 May 2016 23:39:15 +0000 (-0400) Subject: Improve tooltip minification X-Git-Tag: v2.1.5~28^2~3 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=af5344462e64f40c96741bf937f2bffa1fc39a72;p=thirdparty%2FChart.js.git Improve tooltip minification --- diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index 17aa0c9b8..958b0bd10 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -953,17 +953,6 @@ module.exports = function(Chart) { return true; }; - helpers.pushAllIfDefined = function(element, array) { - if (typeof element === "undefined") { - return; - } - - if (helpers.isArray(element)) { - array.push.apply(array, element); - } else { - array.push(element); - } - }; helpers.callCallback = function(fn, args, _tArg) { if (fn && typeof fn.call === 'function') { fn.apply(_tArg, args); diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index 3b594c38c..61c3e9a0b 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -35,12 +35,16 @@ module.exports = function(Chart) { title: function(tooltipItems, data) { // Pick first xLabel for now var title = ''; + var labels = data.labels; + var labelCount = labels ? labels.length : 0; if (tooltipItems.length > 0) { - if (tooltipItems[0].xLabel) { - title = tooltipItems[0].xLabel; - } else if (data.labels.length > 0 && tooltipItems[0].index < data.labels.length) { - title = data.labels[tooltipItems[0].index]; + var item = tooltipItems[0]; + + if (item.xLabel) { + title = item.xLabel; + } else if (labelCount > 0 && item.index < labelCount) { + title = labels[item.index]; } } @@ -91,12 +95,62 @@ module.exports = function(Chart) { return base; } + function getAveragePosition(elements) { + if (!elements.length) { + return false; + } + + var i, len; + var xPositions = []; + var yPositions = []; + + for (i = 0, len = elements.length; i < len; ++i) { + var el = elements[i]; + if (el && el.hasValue()){ + var pos = el.tooltipPosition(); + xPositions.push(pos.x); + yPositions.push(pos.y); + } + } + + var x = 0, + y = 0; + for (i = 0, len - xPositions.length; i < len; ++i) { + x += xPositions[i]; + y += yPositions[i]; + } + + return { + x: Math.round(x / xPositions.length), + y: Math.round(y / xPositions.length) + }; + } + + // Private helper to create a tooltip iteam 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, + datasetIndex = element._datasetIndex; + + return { + xLabel: xScale ? xScale.getLabelForIndex(index, datasetIndex) : '', + yLabel: yScale ? yScale.getLabelForIndex(index, datasetIndex) : '', + index: index, + datasetIndex: datasetIndex + }; + } + Chart.Tooltip = Chart.Element.extend({ initialize: function() { + var me = this; var globalDefaults = Chart.defaults.global; - var tooltipOpts = this._options; + var tooltipOpts = me._options; + var getValueOrDefault = helpers.getValueOrDefault; - helpers.extend(this, { + helpers.extend(me, { _model: { // Positioning xPadding: tooltipOpts.xPadding, @@ -106,26 +160,26 @@ module.exports = function(Chart) { // Body bodyColor: tooltipOpts.bodyColor, - _bodyFontFamily: helpers.getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), - _bodyFontStyle: helpers.getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), + _bodyFontFamily: getValueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily), + _bodyFontStyle: getValueOrDefault(tooltipOpts.bodyFontStyle, globalDefaults.defaultFontStyle), _bodyAlign: tooltipOpts.bodyAlign, - bodyFontSize: helpers.getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), + bodyFontSize: getValueOrDefault(tooltipOpts.bodyFontSize, globalDefaults.defaultFontSize), bodySpacing: tooltipOpts.bodySpacing, // Title titleColor: tooltipOpts.titleColor, - _titleFontFamily: helpers.getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), - _titleFontStyle: helpers.getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), - titleFontSize: helpers.getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), + _titleFontFamily: getValueOrDefault(tooltipOpts.titleFontFamily, globalDefaults.defaultFontFamily), + _titleFontStyle: getValueOrDefault(tooltipOpts.titleFontStyle, globalDefaults.defaultFontStyle), + titleFontSize: getValueOrDefault(tooltipOpts.titleFontSize, globalDefaults.defaultFontSize), _titleAlign: tooltipOpts.titleAlign, titleSpacing: tooltipOpts.titleSpacing, titleMarginBottom: tooltipOpts.titleMarginBottom, // Footer footerColor: tooltipOpts.footerColor, - _footerFontFamily: helpers.getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), - _footerFontStyle: helpers.getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), - footerFontSize: helpers.getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), + _footerFontFamily: getValueOrDefault(tooltipOpts.footerFontFamily, globalDefaults.defaultFontFamily), + _footerFontStyle: getValueOrDefault(tooltipOpts.footerFontStyle, globalDefaults.defaultFontStyle), + footerFontSize: getValueOrDefault(tooltipOpts.footerFontSize, globalDefaults.defaultFontSize), _footerAlign: tooltipOpts.footerAlign, footerSpacing: tooltipOpts.footerSpacing, footerMarginTop: tooltipOpts.footerMarginTop, @@ -143,9 +197,13 @@ module.exports = function(Chart) { // Get the title // Args are: (tooltipItem, data) getTitle: function() { - var beforeTitle = this._options.callbacks.beforeTitle.apply(this, arguments), - title = this._options.callbacks.title.apply(this, arguments), - afterTitle = this._options.callbacks.afterTitle.apply(this, arguments); + var me = this; + var opts = me._options; + var callbacks = opts.callbacks; + + var beforeTitle = callbacks.beforeTitle.apply(me, arguments), + title = callbacks.title.apply(me, arguments), + afterTitle = callbacks.afterTitle.apply(me, arguments); var lines = []; lines = pushOrConcat(lines, beforeTitle); @@ -157,12 +215,15 @@ module.exports = function(Chart) { // Args are: (tooltipItem, data) getBeforeBody: function() { - var lines = this._options.callbacks.beforeBody.apply(this, arguments); + var me = this; + var lines = me._options.callbacks.beforeBody.apply(me, 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) { @@ -171,28 +232,32 @@ module.exports = function(Chart) { lines: [], after: [] }; - helpers.pushAllIfDefined(this._options.callbacks.beforeLabel.call(this, tooltipItem, data), bodyItem.before); - helpers.pushAllIfDefined(this._options.callbacks.label.call(this, tooltipItem, data), bodyItem.lines); - helpers.pushAllIfDefined(this._options.callbacks.afterLabel.call(this, tooltipItem, data), bodyItem.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); - }, this); + }); return bodyItems; }, // Args are: (tooltipItem, data) getAfterBody: function() { - var lines = this._options.callbacks.afterBody.apply(this, arguments); + var me = this; + var lines = me._options.callbacks.afterBody.apply(me, arguments); return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : []; }, // Get the footer and beforeFooter and afterFooter lines // Args are: (tooltipItem, data) getFooter: function() { - var beforeFooter = this._options.callbacks.beforeFooter.apply(this, arguments); - var footer = this._options.callbacks.footer.apply(this, arguments); - var afterFooter = this._options.callbacks.afterFooter.apply(this, arguments); + 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); @@ -202,90 +267,42 @@ module.exports = function(Chart) { return lines; }, - getAveragePosition: function(elements) { - - if (!elements.length) { - return false; - } - - var xPositions = []; - var yPositions = []; - - helpers.each(elements, function(el) { - if (el && el.hasValue()){ - var pos = el.tooltipPosition(); - xPositions.push(pos.x); - yPositions.push(pos.y); - } - }); - - var x = 0, - y = 0; - for (var i = 0; i < xPositions.length; i++) { - x += xPositions[i]; - y += yPositions[i]; - } + update: function(changed) { + var me = this; + var opts = me._options; + var model = me._model; + var active = me._active; + var data = me._data; + var chartInstance = me._chartInstance; - return { - x: Math.round(x / xPositions.length), - y: Math.round(y / xPositions.length) - }; + var i, len; - }, + if (active.length) { + model.opacity = 1; - update: function(changed) { - if (this._active.length) { - this._model.opacity = 1; - - var element = this._active[0], + var element = active[0], labelColors = [], - tooltipPosition; + tooltipPosition = tooltipPosition = getAveragePosition(active); var tooltipItems = []; + for (var i = 0, len = active.length; i < len; ++i) { + tooltipItems.push(createTooltipItem(active[i])); + } - if (this._options.mode === 'single') { - var yScale = element._yScale || element._scale; // handle radar || polarArea charts - tooltipItems.push({ - xLabel: element._xScale ? element._xScale.getLabelForIndex(element._index, element._datasetIndex) : '', - yLabel: yScale ? yScale.getLabelForIndex(element._index, element._datasetIndex) : '', - index: element._index, - datasetIndex: element._datasetIndex - }); - tooltipPosition = this.getAveragePosition(this._active); - } else { - helpers.each(this._data.datasets, function(dataset, datasetIndex) { - if (!this._chartInstance.isDatasetVisible(datasetIndex)) { - return; - } - - var meta = this._chartInstance.getDatasetMeta(datasetIndex); - var currentElement = meta.data[element._index]; - if (currentElement) { - var yScale = element._yScale || element._scale; // handle radar || polarArea charts - - tooltipItems.push({ - xLabel: currentElement._xScale ? currentElement._xScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '', - yLabel: yScale ? yScale.getLabelForIndex(currentElement._index, currentElement._datasetIndex) : '', - index: element._index, - datasetIndex: datasetIndex - }); - } - }, this); - + // If there is more than one item, show color items + if (active.length > 1) { helpers.each(tooltipItems, function(tooltipItem) { - labelColors.push(this._options.callbacks.labelColor.call(this, tooltipItem, this._chartInstance)); - }, this); - - tooltipPosition = this.getAveragePosition(this._active); + labelColors.push(opts.callbacks.labelColor.call(me, tooltipItem, chartInstance)); + }); } // Build the Text Lines - helpers.extend(this._model, { - title: this.getTitle(tooltipItems, this._data), - beforeBody: this.getBeforeBody(tooltipItems, this._data), - body: this.getBody(tooltipItems, this._data), - afterBody: this.getAfterBody(tooltipItems, this._data), - footer: this.getFooter(tooltipItems, this._data), + helpers.extend(model, { + title: me.getTitle(tooltipItems, data), + beforeBody: me.getBeforeBody(tooltipItems, data), + body: me.getBody(tooltipItems, data), + afterBody: me.getAfterBody(tooltipItems, data), + footer: me.getFooter(tooltipItems, data), x: Math.round(tooltipPosition.x), y: Math.round(tooltipPosition.y), caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2), @@ -293,88 +310,103 @@ module.exports = function(Chart) { }); // We need to determine alignment of - var tooltipSize = this.getTooltipSize(this._model); - this.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas + var tooltipSize = me.getTooltipSize(model); + me.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas - helpers.extend(this._model, this.getBackgroundPoint(this._model, tooltipSize)); + helpers.extend(model, me.getBackgroundPoint(model, tooltipSize)); } else { - this._model.opacity = 0; + me._model.opacity = 0; } - if (changed && this._options.custom) { - this._options.custom.call(this, this._model); + if (changed && opts.custom) { + opts.custom.call(me, model); } - return this; + return me; }, getTooltipSize: function getTooltipSize(vm) { - var ctx = this._chart.ctx; + var me = this; + var ctx = me._chart.ctx; var size = { height: vm.yPadding * 2, // Tooltip Padding width: 0 }; - - var combinedBodyLength = vm.body.reduce(function(count, bodyItem) { + // Count of all lines in the body + var body = vm.body; + var combinedBodyLength = body.reduce(function(count, bodyItem) { return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length; }, 0); - // Count in before and after body sections combinedBodyLength += vm.beforeBody.length + vm.afterBody.length; - size.height += vm.title.length * vm.titleFontSize; // Title Lines - size.height += (vm.title.length - 1) * vm.titleSpacing; // Title Line Spacing - size.height += vm.title.length ? vm.titleMarginBottom : 0; // Title's bottom Margin - size.height += combinedBodyLength * vm.bodyFontSize; // Body Lines + var titleLineCount = vm.title.length; + var footerLineCount = vm.footer.length; + var titleFontSize = vm.titleFontSize, + bodyFontSize = vm.bodyFontSize, + footerFontSize = vm.footerFontSize; + + size.height += titleLineCount * titleFontSize; // Title Lines + size.height += (titleLineCount - 1) * vm.titleSpacing; // Title Line Spacing + size.height += titleLineCount ? vm.titleMarginBottom : 0; // Title's bottom Margin + size.height += combinedBodyLength * bodyFontSize; // Body Lines size.height += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing - size.height += vm.footer.length ? vm.footerMarginTop : 0; // Footer Margin - size.height += vm.footer.length * (vm.footerFontSize); // Footer Lines - size.height += vm.footer.length ? (vm.footer.length - 1) * vm.footerSpacing : 0; // Footer Line Spacing - - // Width - ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily); - helpers.each(vm.title, function(line) { - size.width = Math.max(size.width, ctx.measureText(line).width); - }); + size.height += footerLineCount ? vm.footerMarginTop : 0; // Footer Margin + size.height += footerLineCount * (footerFontSize); // Footer Lines + size.height += footerLineCount ? (footerLineCount - 1) * vm.footerSpacing : 0; // Footer Line Spacing + + // Title width + var widthPadding = 0; + var maxLineWidth = function(line) { + size.width = Math.max(size.width, ctx.measureText(line).width + widthPadding); + }; - ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); - helpers.each(vm.beforeBody.concat(vm.afterBody), function(line) { - size.width = Math.max(size.width, ctx.measureText(line).width); - }); + ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + helpers.each(vm.title, maxLineWidth); - var _this = this; - var maxBodyWidth = function(line) { - size.width = Math.max(size.width, ctx.measureText(line).width + (_this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0)); - }; - helpers.each(vm.body, function(bodyItem) { - helpers.each(bodyItem.before, maxBodyWidth); - helpers.each(bodyItem.lines, maxBodyWidth); - helpers.each(bodyItem.after, maxBodyWidth); - }); + // Body width + ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + helpers.each(vm.beforeBody.concat(vm.afterBody), maxLineWidth); - ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); - helpers.each(vm.footer, function(line) { - size.width = Math.max(size.width, ctx.measureText(line).width); + // Body lines may include some extra width due to the color box + widthPadding = body.length > 1 ? (bodyFontSize + 2) : 0; + helpers.each(body, function(bodyItem) { + helpers.each(bodyItem.before, maxLineWidth); + helpers.each(bodyItem.lines, maxLineWidth); + helpers.each(bodyItem.after, maxLineWidth); }); + + // Reset back to 0 + widthPadding = 0; + + // Footer width + ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily); + helpers.each(vm.footer, maxLineWidth); + + // Add padding size.width += 2 * vm.xPadding; return size; }, determineAlignment: function determineAlignment(size) { - if (this._model.y < size.height) { - this._model.yAlign = 'top'; - } else if (this._model.y > (this._chart.height - size.height)) { - this._model.yAlign = 'bottom'; + var me = this; + var model = me._model; + var chart = me._chart; + var chartArea = me._chartInstance.chartArea; + + if (model.y < size.height) { + model.yAlign = 'top'; + } else if (model.y > (chart.height - size.height)) { + model.yAlign = 'bottom'; } 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 _this = this; - var midX = (this._chartInstance.chartArea.left + this._chartInstance.chartArea.right) / 2; - var midY = (this._chartInstance.chartArea.top + this._chartInstance.chartArea.bottom) / 2; + var midX = (chartArea.left + chartArea.right) / 2; + var midY = (chartArea.top + chartArea.bottom) / 2; - if (this._model.yAlign === 'center') { + if (model.yAlign === 'center') { lf = function(x) { return x <= midX; }; @@ -386,12 +418,12 @@ module.exports = function(Chart) { return x <= (size.width / 2); }; rf = function(x) { - return x >= (_this._chart.width - (size.width / 2)); + return x >= (chart.width - (size.width / 2)); }; } olf = function(x) { - return x + size.width > _this._chart.width; + return x + size.width > chart.width; }; orf = function(x) { return x - size.width < 0; @@ -400,21 +432,21 @@ module.exports = function(Chart) { return y <= midY ? 'top' : 'bottom'; }; - if (lf(this._model.x)) { - this._model.xAlign = 'left'; + if (lf(model.x)) { + model.xAlign = 'left'; // Is tooltip too wide and goes over the right side of the chart.? - if (olf(this._model.x)) { - this._model.xAlign = 'center'; - this._model.yAlign = yf(this._model.y); + if (olf(model.x)) { + model.xAlign = 'center'; + model.yAlign = yf(model.y); } - } else if (rf(this._model.x)) { - this._model.xAlign = 'right'; + } else if (rf(model.x)) { + model.xAlign = 'right'; // Is tooltip too wide and goes outside left edge of canvas? - if (orf(this._model.x)) { - this._model.xAlign = 'center'; - this._model.yAlign = yf(this._model.y); + if (orf(model.x)) { + model.xAlign = 'center'; + model.yAlign = yf(model.y); } } }, @@ -425,79 +457,96 @@ module.exports = function(Chart) { y: vm.y }; - if (vm.xAlign === 'right') { + var caretSize = vm.caretSize, + caretPadding = vm.caretPadding, + cornerRadius = vm.cornerRadius, + xAlign = vm.xAlign, + yAlign = vm.yAlign, + paddingAndSize = caretSize + caretPadding, + radiusAndPadding = cornerRadius + caretPadding; + + if (xAlign === 'right') { pt.x -= size.width; - } else if (vm.xAlign === 'center') { + } else if (xAlign === 'center') { pt.x -= (size.width / 2); } - if (vm.yAlign === 'top') { - pt.y += vm.caretPadding + vm.caretSize; - } else if (vm.yAlign === 'bottom') { - pt.y -= size.height + vm.caretPadding + vm.caretSize; + if (yAlign === 'top') { + pt.y += paddingAndSize; + } else if (yAlign === 'bottom') { + pt.y -= size.height + paddingAndSize; } else { pt.y -= (size.height / 2); } - if (vm.yAlign === 'center') { - if (vm.xAlign === 'left') { - pt.x += vm.caretPadding + vm.caretSize; - } else if (vm.xAlign === 'right') { - pt.x -= vm.caretPadding + vm.caretSize; + if (yAlign === 'center') { + if (xAlign === 'left') { + pt.x += paddingAndSize; + } else if (xAlign === 'right') { + pt.x -= paddingAndSize; } } else { - if (vm.xAlign === 'left') { - pt.x -= vm.cornerRadius + vm.caretPadding; - } else if (vm.xAlign === 'right') { - pt.x += vm.cornerRadius + vm.caretPadding; + if (xAlign === 'left') { + pt.x -= radiusAndPadding; + } else if (xAlign === 'right') { + pt.x += radiusAndPadding; } } return pt; }, drawCaret: function drawCaret(tooltipPoint, size, opacity, caretPadding) { - var vm = this._view; - var ctx = this._chart.ctx; + var me = this; + var vm = me._view; + var ctx = me._chart.ctx; var x1, x2, x3; var y1, y2, y3; - - if (vm.yAlign === 'center') { + var caretSize = vm.caretSize; + var cornerRadius = vm.cornerRadius; + var xAlign = vm.xAlign, + yAlign = vm.yAlign; + var ptX = tooltipPoint.x, + ptY = tooltipPoint.y; + var width = size.width, + height = size.height; + + if (yAlign === 'center') { // Left or right side - if (vm.xAlign === 'left') { - x1 = tooltipPoint.x; - x2 = x1 - vm.caretSize; + if (xAlign === 'left') { + x1 = ptX; + x2 = x1 - caretSize; x3 = x1; } else { - x1 = tooltipPoint.x + size.width; - x2 = x1 + vm.caretSize; + x1 = ptX + width; + x2 = x1 + caretSize; x3 = x1; } - y2 = tooltipPoint.y + (size.height / 2); - y1 = y2 - vm.caretSize; - y3 = y2 + vm.caretSize; + y2 = ptY + (height / 2); + y1 = y2 - caretSize; + y3 = y2 + caretSize; } else { - if (vm.xAlign === 'left') { - x1 = tooltipPoint.x + vm.cornerRadius; - x2 = x1 + vm.caretSize; - x3 = x2 + vm.caretSize; - } else if (vm.xAlign === 'right') { - x1 = tooltipPoint.x + size.width - vm.cornerRadius; - x2 = x1 - vm.caretSize; - x3 = x2 - vm.caretSize; + if (xAlign === 'left') { + x1 = ptX + cornerRadius; + x2 = x1 + caretSize; + x3 = x2 + caretSize; + } else if (xAlign === 'right') { + x1 = ptX + width - cornerRadius; + x2 = x1 - caretSize; + x3 = x2 - caretSize; } else { - x2 = tooltipPoint.x + (size.width / 2); - x1 = x2 - vm.caretSize; - x3 = x2 + vm.caretSize; + x2 = ptX + (width / 2); + x1 = x2 - caretSize; + x3 = x2 + caretSize; } - if (vm.yAlign === 'top') { - y1 = tooltipPoint.y; - y2 = y1 - vm.caretSize; + if (yAlign === 'top') { + y1 = ptY; + y2 = y1 - caretSize; y3 = y1; } else { - y1 = tooltipPoint.y + size.height; - y2 = y1 + vm.caretSize; + y1 = ptY + height; + y2 = y1 + caretSize; y3 = y1; } } @@ -512,82 +561,96 @@ module.exports = function(Chart) { ctx.fill(); }, drawTitle: function drawTitle(pt, vm, ctx, opacity) { - if (vm.title.length) { + var title = vm.title; + + if (title.length) { ctx.textAlign = vm._titleAlign; ctx.textBaseline = "top"; + var titleFontSize = vm.titleFontSize, + titleSpacing = vm.titleSpacing; + var titleColor = helpers.color(vm.titleColor); ctx.fillStyle = titleColor.alpha(opacity * titleColor.alpha()).rgbString(); - ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily); + ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); - helpers.each(vm.title, function(title, i) { - ctx.fillText(title, pt.x, pt.y); - pt.y += vm.titleFontSize + vm.titleSpacing; // Line Height and spacing + 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 - if (i + 1 === vm.title.length) { - pt.y += vm.titleMarginBottom - vm.titleSpacing; // If Last, add margin, remove spacing + if (i + 1 === title.length) { + pt.y += vm.titleMarginBottom - titleSpacing; // If Last, add margin, remove spacing } - }); + } } }, drawBody: function drawBody(pt, vm, ctx, opacity) { + var me = this; + var bodyFontSize = vm.bodyFontSize; + var bodySpacing = vm.bodySpacing; + var body = vm.body; + ctx.textAlign = vm._bodyAlign; ctx.textBaseline = "top"; var bodyColor = helpers.color(vm.bodyColor); - ctx.fillStyle = bodyColor.alpha(opacity * bodyColor.alpha()).rgbString(); - ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); + var textColor = bodyColor.alpha(opacity * bodyColor.alpha()).rgbString(); + ctx.fillStyle = textColor + ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily); // Before Body - helpers.each(vm.beforeBody, function(beforeBody) { - ctx.fillText(beforeBody, pt.x, pt.y); - pt.y += vm.bodyFontSize + vm.bodySpacing; - }); + var xLinePadding = 0; + var fillLineOfText = function(line) { + ctx.fillText(line, pt.x + xLinePadding, pt.y); + pt.y += bodyFontSize + bodySpacing; + }; - helpers.each(vm.body, function(bodyItem, i) { - var _this = this; - var fillLine = function(line) { - // Body Line - ctx.fillText(line, pt.x + (_this._options.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y); - pt.y += vm.bodyFontSize + vm.bodySpacing; - }; + // Before body lines + helpers.each(vm.beforeBody, fillLineOfText); - helpers.each(bodyItem.before, fillLine); + var drawColorBoxes = body.length > 1; + xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0 + + // Draw body lines now + helpers.each(body, function(bodyItem, i) { + helpers.each(bodyItem.before, fillLineOfText); helpers.each(bodyItem.lines, function(line) { // Draw Legend-like boxes if needed - if (this._options.mode !== 'single') { + if (drawColorBoxes) { // Fill a white rect so that colours merge nicely if the opacity is < 1 ctx.fillStyle = helpers.color(vm.legendColorBackground).alpha(opacity).rgbaString(); - ctx.fillRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize); + ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); // Border ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString(); - ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.bodyFontSize); + ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); // Inner square ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString(); - ctx.fillRect(pt.x + 1, pt.y + 1, vm.bodyFontSize - 2, vm.bodyFontSize - 2); + ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); - ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text + ctx.fillStyle = textColor; } - fillLine(line); - }, this); + fillLineOfText(line); + }); - helpers.each(bodyItem.after, fillLine); - }, this); - - // After Body - helpers.each(vm.afterBody, function(afterBody) { - ctx.fillText(afterBody, pt.x, pt.y); - pt.y += vm.bodyFontSize; + helpers.each(bodyItem.after, fillLineOfText); }); - pt.y -= vm.bodySpacing; // Remove last body spacing + // Reset back to 0 for after body + xLinePadding = 0; + + // After body lines + helpers.each(vm.afterBody, fillLineOfText); + pt.y -= bodySpacing; // Remove last body spacing }, drawFooter: function drawFooter(pt, vm, ctx, opacity) { - if (vm.footer.length) { + var footer = vm.footer; + + if (footer.length) { pt.y += vm.footerMarginTop; ctx.textAlign = vm._footerAlign; @@ -597,8 +660,8 @@ module.exports = function(Chart) { ctx.fillStyle = footerColor.alpha(opacity * footerColor.alpha()).rgbString(); ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); - helpers.each(vm.footer, function(footer) { - ctx.fillText(footer, pt.x, pt.y); + helpers.each(footer, function(line) { + ctx.fillText(line, pt.x, pt.y); pt.y += vm.footerFontSize + vm.footerSpacing; }); } @@ -611,7 +674,6 @@ module.exports = function(Chart) { return; } - var caretPadding = vm.caretPadding; var tooltipSize = this.getTooltipSize(vm); var pt = { x: vm.x, @@ -629,7 +691,7 @@ module.exports = function(Chart) { ctx.fill(); // Draw Caret - this.drawCaret(pt, tooltipSize, opacity, caretPadding); + this.drawCaret(pt, tooltipSize, opacity, vm.caretPadding); // Draw Title, Body, and Footer pt.x += vm.xPadding;