mirror of
https://github.com/chartjs/Chart.js.git
synced 2024-10-07 12:49:07 +02:00
82b1e5cd99
Introduced a new meta.hidden 3 states flag (null|true|false) to be able to override dataset.hidden when interacting with the chart (i.e., true or false to ignore the dataset.hidden value). This is required in order to be able to correctly share dataset.hidden between multiple charts. For example: 2 charts are sharing the same data and dataset.hidden is initially false: the dataset will be displayed on both charts because meta.hidden is null. If the user clicks the legend of the first chart, meta.hidden is changed to true and the dataset is only hidden on the first chart. If dataset.hidden changes, only the second chart will have the dataset visibility updated and that until the user click again on the first chart legend, switching the meta.hidden to null.
617 lines
19 KiB
JavaScript
617 lines
19 KiB
JavaScript
"use strict";
|
|
|
|
module.exports = function(Chart) {
|
|
|
|
var helpers = Chart.helpers;
|
|
|
|
Chart.defaults.global.tooltips = {
|
|
enabled: true,
|
|
custom: null,
|
|
mode: 'single',
|
|
backgroundColor: "rgba(0,0,0,0.8)",
|
|
titleFontStyle: "bold",
|
|
titleSpacing: 2,
|
|
titleMarginBottom: 6,
|
|
titleColor: "#fff",
|
|
titleAlign: "left",
|
|
bodySpacing: 2,
|
|
bodyColor: "#fff",
|
|
bodyAlign: "left",
|
|
footerFontStyle: "bold",
|
|
footerSpacing: 2,
|
|
footerMarginTop: 6,
|
|
footerColor: "#fff",
|
|
footerAlign: "left",
|
|
yPadding: 6,
|
|
xPadding: 6,
|
|
yAlign : 'center',
|
|
xAlign : 'center',
|
|
caretSize: 5,
|
|
cornerRadius: 6,
|
|
multiKeyBackground: '#fff',
|
|
callbacks: {
|
|
// Args are: (tooltipItems, data)
|
|
beforeTitle: helpers.noop,
|
|
title: function(tooltipItems, data) {
|
|
// Pick first xLabel for now
|
|
var title = '';
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
return title;
|
|
},
|
|
afterTitle: helpers.noop,
|
|
|
|
// Args are: (tooltipItems, data)
|
|
beforeBody: helpers.noop,
|
|
|
|
// Args are: (tooltipItem, data)
|
|
beforeLabel: helpers.noop,
|
|
label: function(tooltipItem, data) {
|
|
var datasetLabel = data.datasets[tooltipItem.datasetIndex].label || '';
|
|
return datasetLabel + ': ' + tooltipItem.yLabel;
|
|
},
|
|
afterLabel: helpers.noop,
|
|
|
|
// Args are: (tooltipItems, data)
|
|
afterBody: helpers.noop,
|
|
|
|
// Args are: (tooltipItems, data)
|
|
beforeFooter: helpers.noop,
|
|
footer: helpers.noop,
|
|
afterFooter: helpers.noop
|
|
}
|
|
};
|
|
|
|
// 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);
|
|
} else {
|
|
base.push(toPush);
|
|
}
|
|
}
|
|
|
|
return base;
|
|
}
|
|
|
|
Chart.Tooltip = Chart.Element.extend({
|
|
initialize: function() {
|
|
var options = this._options;
|
|
helpers.extend(this, {
|
|
_model: {
|
|
// Positioning
|
|
xPadding: options.tooltips.xPadding,
|
|
yPadding: options.tooltips.yPadding,
|
|
xAlign : options.tooltips.yAlign,
|
|
yAlign : options.tooltips.xAlign,
|
|
|
|
// Body
|
|
bodyColor: options.tooltips.bodyColor,
|
|
_bodyFontFamily: helpers.getValueOrDefault(options.tooltips.bodyFontFamily, Chart.defaults.global.defaultFontFamily),
|
|
_bodyFontStyle: helpers.getValueOrDefault(options.tooltips.bodyFontStyle, Chart.defaults.global.defaultFontStyle),
|
|
_bodyAlign: options.tooltips.bodyAlign,
|
|
bodyFontSize: helpers.getValueOrDefault(options.tooltips.bodyFontSize, Chart.defaults.global.defaultFontSize),
|
|
bodySpacing: options.tooltips.bodySpacing,
|
|
|
|
// Title
|
|
titleColor: options.tooltips.titleColor,
|
|
_titleFontFamily: helpers.getValueOrDefault(options.tooltips.titleFontFamily, Chart.defaults.global.defaultFontFamily),
|
|
_titleFontStyle: helpers.getValueOrDefault(options.tooltips.titleFontStyle, Chart.defaults.global.defaultFontStyle),
|
|
titleFontSize: helpers.getValueOrDefault(options.tooltips.titleFontSize, Chart.defaults.global.defaultFontSize),
|
|
_titleAlign: options.tooltips.titleAlign,
|
|
titleSpacing: options.tooltips.titleSpacing,
|
|
titleMarginBottom: options.tooltips.titleMarginBottom,
|
|
|
|
// Footer
|
|
footerColor: options.tooltips.footerColor,
|
|
_footerFontFamily: helpers.getValueOrDefault(options.tooltips.footerFontFamily, Chart.defaults.global.defaultFontFamily),
|
|
_footerFontStyle: helpers.getValueOrDefault(options.tooltips.footerFontStyle, Chart.defaults.global.defaultFontStyle),
|
|
footerFontSize: helpers.getValueOrDefault(options.tooltips.footerFontSize, Chart.defaults.global.defaultFontSize),
|
|
_footerAlign: options.tooltips.footerAlign,
|
|
footerSpacing: options.tooltips.footerSpacing,
|
|
footerMarginTop: options.tooltips.footerMarginTop,
|
|
|
|
// Appearance
|
|
caretSize: options.tooltips.caretSize,
|
|
cornerRadius: options.tooltips.cornerRadius,
|
|
backgroundColor: options.tooltips.backgroundColor,
|
|
opacity: 0,
|
|
legendColorBackground: options.tooltips.multiKeyBackground
|
|
}
|
|
});
|
|
},
|
|
|
|
// Get the title
|
|
// Args are: (tooltipItem, data)
|
|
getTitle: function() {
|
|
var beforeTitle = this._options.tooltips.callbacks.beforeTitle.apply(this, arguments),
|
|
title = this._options.tooltips.callbacks.title.apply(this, arguments),
|
|
afterTitle = this._options.tooltips.callbacks.afterTitle.apply(this, 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.tooltips.callbacks.beforeBody.apply(this, arguments);
|
|
return helpers.isArray(lines) ? lines : lines !== undefined ? [lines] : [];
|
|
},
|
|
|
|
// Args are: (tooltipItem, data)
|
|
getBody: function(tooltipItems, data) {
|
|
var lines = [];
|
|
|
|
helpers.each(tooltipItems, function(bodyItem) {
|
|
helpers.pushAllIfDefined(this._options.tooltips.callbacks.beforeLabel.call(this, bodyItem, data), lines);
|
|
helpers.pushAllIfDefined(this._options.tooltips.callbacks.label.call(this, bodyItem, data), lines);
|
|
helpers.pushAllIfDefined(this._options.tooltips.callbacks.afterLabel.call(this, bodyItem, data), lines);
|
|
}, this);
|
|
|
|
return lines;
|
|
},
|
|
|
|
// Args are: (tooltipItem, data)
|
|
getAfterBody: function() {
|
|
var lines = this._options.tooltips.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 beforeFooter = this._options.tooltips.callbacks.beforeFooter.apply(this, arguments);
|
|
var footer = this._options.tooltips.callbacks.footer.apply(this, arguments);
|
|
var afterFooter = this._options.tooltips.callbacks.afterFooter.apply(this, arguments);
|
|
|
|
var lines = [];
|
|
lines = pushOrConcat(lines, beforeFooter);
|
|
lines = pushOrConcat(lines, footer);
|
|
lines = pushOrConcat(lines, afterFooter);
|
|
|
|
return lines;
|
|
},
|
|
|
|
getAveragePosition: function(elements) {
|
|
|
|
if (!elements.length) {
|
|
return false;
|
|
}
|
|
|
|
var xPositions = [];
|
|
var yPositions = [];
|
|
|
|
helpers.each(elements, function(el) {
|
|
if (el) {
|
|
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];
|
|
}
|
|
|
|
return {
|
|
x: Math.round(x / xPositions.length),
|
|
y: Math.round(y / xPositions.length)
|
|
};
|
|
|
|
},
|
|
|
|
update: function(changed) {
|
|
if (this._active.length) {
|
|
this._model.opacity = 1;
|
|
|
|
var element = this._active[0],
|
|
labelColors = [],
|
|
tooltipPosition;
|
|
|
|
var tooltipItems = [];
|
|
|
|
if (this._options.tooltips.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);
|
|
|
|
helpers.each(this._active, function(active) {
|
|
if (active) {
|
|
labelColors.push({
|
|
borderColor: active._view.borderColor,
|
|
backgroundColor: active._view.backgroundColor
|
|
});
|
|
}
|
|
}, null);
|
|
|
|
tooltipPosition = this.getAveragePosition(this._active);
|
|
}
|
|
|
|
// 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(this._model, {
|
|
x: Math.round(tooltipPosition.x),
|
|
y: Math.round(tooltipPosition.y),
|
|
caretPadding: helpers.getValueOrDefault(tooltipPosition.padding, 2),
|
|
labelColors: labelColors
|
|
});
|
|
|
|
// We need to determine alignment of
|
|
var tooltipSize = this.getTooltipSize(this._model);
|
|
this.determineAlignment(tooltipSize); // Smart Tooltip placement to stay on the canvas
|
|
|
|
helpers.extend(this._model, this.getBackgroundPoint(this._model, tooltipSize));
|
|
} else {
|
|
this._model.opacity = 0;
|
|
}
|
|
|
|
if (changed && this._options.tooltips.custom) {
|
|
this._options.tooltips.custom.call(this, this._model);
|
|
}
|
|
|
|
return this;
|
|
},
|
|
getTooltipSize: function getTooltipSize(vm) {
|
|
var ctx = this._chart.ctx;
|
|
|
|
var size = {
|
|
height: vm.yPadding * 2, // Tooltip Padding
|
|
width: 0
|
|
};
|
|
var combinedBodyLength = vm.body.length + 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
|
|
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);
|
|
});
|
|
|
|
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);
|
|
});
|
|
helpers.each(vm.body, function(line) {
|
|
size.width = Math.max(size.width, ctx.measureText(line).width + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0));
|
|
}, this);
|
|
|
|
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);
|
|
});
|
|
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 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;
|
|
|
|
if (this._model.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 >= (_this._chart.width - (size.width / 2));
|
|
};
|
|
}
|
|
|
|
olf = function(x) {
|
|
return x + size.width > _this._chart.width;
|
|
};
|
|
orf = function(x) {
|
|
return x - size.width < 0;
|
|
};
|
|
yf = function(y) {
|
|
return y <= midY ? 'top' : 'bottom';
|
|
};
|
|
|
|
if (lf(this._model.x)) {
|
|
this._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);
|
|
}
|
|
} else if (rf(this._model.x)) {
|
|
this._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);
|
|
}
|
|
}
|
|
},
|
|
getBackgroundPoint: function getBackgroundPoint(vm, size) {
|
|
// Background Position
|
|
var pt = {
|
|
x: vm.x,
|
|
y: vm.y
|
|
};
|
|
|
|
if (vm.xAlign === 'right') {
|
|
pt.x -= size.width;
|
|
} else if (vm.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;
|
|
} 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;
|
|
}
|
|
} else {
|
|
if (vm.xAlign === 'left') {
|
|
pt.x -= vm.cornerRadius + vm.caretPadding;
|
|
} else if (vm.xAlign === 'right') {
|
|
pt.x += vm.cornerRadius + vm.caretPadding;
|
|
}
|
|
}
|
|
|
|
return pt;
|
|
},
|
|
drawCaret: function drawCaret(tooltipPoint, size, opacity, caretPadding) {
|
|
var vm = this._view;
|
|
var ctx = this._chart.ctx;
|
|
var x1, x2, x3;
|
|
var y1, y2, y3;
|
|
|
|
if (vm.yAlign === 'center') {
|
|
// Left or right side
|
|
if (vm.xAlign === 'left') {
|
|
x1 = tooltipPoint.x;
|
|
x2 = x1 - vm.caretSize;
|
|
x3 = x1;
|
|
} else {
|
|
x1 = tooltipPoint.x + size.width;
|
|
x2 = x1 + vm.caretSize;
|
|
x3 = x1;
|
|
}
|
|
|
|
y2 = tooltipPoint.y + (size.height / 2);
|
|
y1 = y2 - vm.caretSize;
|
|
y3 = y2 + vm.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;
|
|
} else {
|
|
x2 = tooltipPoint.x + (size.width / 2);
|
|
x1 = x2 - vm.caretSize;
|
|
x3 = x2 + vm.caretSize;
|
|
}
|
|
|
|
if (vm.yAlign === 'top') {
|
|
y1 = tooltipPoint.y;
|
|
y2 = y1 - vm.caretSize;
|
|
y3 = y1;
|
|
} else {
|
|
y1 = tooltipPoint.y + size.height;
|
|
y2 = y1 + vm.caretSize;
|
|
y3 = y1;
|
|
}
|
|
}
|
|
|
|
var bgColor = helpers.color(vm.backgroundColor);
|
|
ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
|
|
ctx.beginPath();
|
|
ctx.moveTo(x1, y1);
|
|
ctx.lineTo(x2, y2);
|
|
ctx.lineTo(x3, y3);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
},
|
|
drawTitle: function drawTitle(pt, vm, ctx, opacity) {
|
|
if (vm.title.length) {
|
|
ctx.textAlign = vm._titleAlign;
|
|
ctx.textBaseline = "top";
|
|
|
|
var titleColor = helpers.color(vm.titleColor);
|
|
ctx.fillStyle = titleColor.alpha(opacity * titleColor.alpha()).rgbString();
|
|
ctx.font = helpers.fontString(vm.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
|
|
|
|
if (i + 1 === vm.title.length) {
|
|
pt.y += vm.titleMarginBottom - vm.titleSpacing; // If Last, add margin, remove spacing
|
|
}
|
|
});
|
|
}
|
|
},
|
|
drawBody: function drawBody(pt, vm, ctx, opacity) {
|
|
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);
|
|
|
|
// Before Body
|
|
helpers.each(vm.beforeBody, function(beforeBody) {
|
|
ctx.fillText(beforeBody, pt.x, pt.y);
|
|
pt.y += vm.bodyFontSize + vm.bodySpacing;
|
|
});
|
|
|
|
helpers.each(vm.body, function(body, i) {
|
|
// Draw Legend-like boxes if needed
|
|
if (this._options.tooltips.mode !== 'single') {
|
|
// 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);
|
|
|
|
// Border
|
|
ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
|
|
ctx.strokeRect(pt.x, pt.y, vm.bodyFontSize, vm.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.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbaString(); // Return fill style for text
|
|
}
|
|
|
|
// Body Line
|
|
ctx.fillText(body, pt.x + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), pt.y);
|
|
|
|
pt.y += vm.bodyFontSize + vm.bodySpacing;
|
|
}, this);
|
|
|
|
// After Body
|
|
helpers.each(vm.afterBody, function(afterBody) {
|
|
ctx.fillText(afterBody, pt.x, pt.y);
|
|
pt.y += vm.bodyFontSize;
|
|
});
|
|
|
|
pt.y -= vm.bodySpacing; // Remove last body spacing
|
|
},
|
|
drawFooter: function drawFooter(pt, vm, ctx, opacity) {
|
|
if (vm.footer.length) {
|
|
pt.y += vm.footerMarginTop;
|
|
|
|
ctx.textAlign = vm._footerAlign;
|
|
ctx.textBaseline = "top";
|
|
|
|
var footerColor = helpers.color(vm.footerColor);
|
|
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);
|
|
pt.y += vm.footerFontSize + vm.footerSpacing;
|
|
});
|
|
}
|
|
},
|
|
draw: function draw() {
|
|
var ctx = this._chart.ctx;
|
|
var vm = this._view;
|
|
|
|
if (vm.opacity === 0) {
|
|
return;
|
|
}
|
|
|
|
var caretPadding = vm.caretPadding;
|
|
var tooltipSize = this.getTooltipSize(vm);
|
|
var pt = {
|
|
x: vm.x,
|
|
y: vm.y
|
|
};
|
|
|
|
// IE11/Edge does not like very small opacities, so snap to 0
|
|
var opacity = Math.abs(vm.opacity < 1e-3) ? 0 : vm.opacity;
|
|
|
|
if (this._options.tooltips.enabled) {
|
|
// Draw Background
|
|
var bgColor = helpers.color(vm.backgroundColor);
|
|
ctx.fillStyle = bgColor.alpha(opacity * bgColor.alpha()).rgbString();
|
|
helpers.drawRoundedRectangle(ctx, pt.x, pt.y, tooltipSize.width, tooltipSize.height, vm.cornerRadius);
|
|
ctx.fill();
|
|
|
|
// Draw Caret
|
|
this.drawCaret(pt, tooltipSize, opacity, caretPadding);
|
|
|
|
// Draw Title, Body, and Footer
|
|
pt.x += vm.xPadding;
|
|
pt.y += vm.yPadding;
|
|
|
|
// Titles
|
|
this.drawTitle(pt, vm, ctx, opacity);
|
|
|
|
// Body
|
|
this.drawBody(pt, vm, ctx, opacity);
|
|
|
|
// Footer
|
|
this.drawFooter(pt, vm, ctx, opacity);
|
|
}
|
|
}
|
|
});
|
|
};
|