mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Initial refactor of tooltip code
This commit is contained in:
parent
d26ab24e3c
commit
d7643bf865
@ -297,227 +297,231 @@
|
||||
|
||||
return this;
|
||||
},
|
||||
draw: function() {
|
||||
|
||||
|
||||
var ctx = this._chart.ctx;
|
||||
getTooltipSize: function getTooltipSize() {
|
||||
var vm = this._view;
|
||||
var ctx = this._chart.ctx;
|
||||
|
||||
if (this._view.opacity === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get Dimensions
|
||||
|
||||
vm.position = "top";
|
||||
|
||||
var caretPadding = vm.caretPadding || 2;
|
||||
|
||||
var size = {
|
||||
height: vm.yPadding * 2, // Tooltip Padding
|
||||
width: 0
|
||||
};
|
||||
var combinedBodyLength = vm.body.length + vm.beforeBody.length + vm.afterBody.length;
|
||||
|
||||
// Height
|
||||
var tooltipHeight = vm.yPadding * 2; // Tooltip Padding
|
||||
|
||||
tooltipHeight += vm.title.length * vm.titleFontSize; // Title Lines
|
||||
tooltipHeight += (vm.title.length - 1) * vm.titleSpacing; // Title Line Spacing
|
||||
tooltipHeight += vm.title.length ? vm.titleMarginBottom : 0; // Title's bottom Margin
|
||||
|
||||
tooltipHeight += combinedBodyLength * vm.bodyFontSize; // Body Lines
|
||||
tooltipHeight += combinedBodyLength ? (combinedBodyLength - 1) * vm.bodySpacing : 0; // Body Line Spacing
|
||||
|
||||
tooltipHeight += vm.footer.length ? vm.footerMarginTop : 0; // Footer Margin
|
||||
tooltipHeight += vm.footer.length * (vm.footerFontSize); // Footer Lines
|
||||
tooltipHeight += vm.footer.length ? (vm.footer.length - 1) * vm.footerSpacing : 0; // Footer Line Spacing
|
||||
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
|
||||
var tooltipWidth = 0;
|
||||
ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
|
||||
helpers.each(vm.title, function(line) {
|
||||
ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
|
||||
tooltipWidth = Math.max(tooltipWidth, ctx.measureText(line).width);
|
||||
size.width = Math.max(size.width, ctx.measureText(line).width);
|
||||
});
|
||||
helpers.each(vm.beforeBody, function(line) {
|
||||
ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
|
||||
tooltipWidth = Math.max(tooltipWidth, 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);
|
||||
}, this);
|
||||
helpers.each(vm.body, function(line) {
|
||||
ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
|
||||
tooltipWidth = Math.max(tooltipWidth, ctx.measureText(line).width + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0));
|
||||
}, this);
|
||||
helpers.each(vm.afterBody, function(line) {
|
||||
ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
|
||||
tooltipWidth = Math.max(tooltipWidth, ctx.measureText(line).width);
|
||||
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) {
|
||||
ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
|
||||
tooltipWidth = Math.max(tooltipWidth, ctx.measureText(line).width);
|
||||
size.width = Math.max(size.width, ctx.measureText(line).width);
|
||||
});
|
||||
tooltipWidth += 2 * vm.xPadding;
|
||||
var tooltipTotalWidth = tooltipWidth + vm.caretSize + caretPadding;
|
||||
size.width += 2 * vm.xPadding;
|
||||
|
||||
|
||||
|
||||
// Smart Tooltip placement to stay on the canvas
|
||||
// Top, center, or bottom
|
||||
return size;
|
||||
},
|
||||
determineAlignment: function determineAlignment(size) {
|
||||
var vm = this._view;
|
||||
vm.yAlign = "center";
|
||||
if (vm.y - (tooltipHeight / 2) < 0) {
|
||||
if (vm.y - (size.height / 2) < 0) {
|
||||
vm.yAlign = "top";
|
||||
} else if (vm.y + (tooltipHeight / 2) > this._chart.height) {
|
||||
} else if (vm.y + (size.height / 2) > this._chart.height) {
|
||||
vm.yAlign = "bottom";
|
||||
}
|
||||
|
||||
|
||||
// Left or Right
|
||||
vm.xAlign = "right";
|
||||
if (vm.x + tooltipTotalWidth > this._chart.width) {
|
||||
if (vm.x + size.width > this._chart.width) {
|
||||
vm.xAlign = "left";
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
getBackgroundPoint: function getBackgroundPoint(size, caretPadding) {
|
||||
var vm = this._view;
|
||||
// Background Position
|
||||
var tooltipX = vm.x,
|
||||
tooltipY = vm.y;
|
||||
var pt = {
|
||||
x: vm.x,
|
||||
y: vm.y
|
||||
};
|
||||
|
||||
if (vm.yAlign === 'top') {
|
||||
tooltipY = vm.y - vm.caretSize - vm.cornerRadius;
|
||||
pt.y = vm.y - vm.caretSize - vm.cornerRadius;
|
||||
} else if (vm.yAlign === 'bottom') {
|
||||
tooltipY = vm.y - tooltipHeight + vm.caretSize + vm.cornerRadius;
|
||||
pt.y = vm.y - size.height + vm.caretSize + vm.cornerRadius;
|
||||
} else {
|
||||
tooltipY = vm.y - (tooltipHeight / 2);
|
||||
pt.y = vm.y - (size.height / 2);
|
||||
}
|
||||
|
||||
if (vm.xAlign === 'left') {
|
||||
tooltipX = vm.x - tooltipTotalWidth;
|
||||
pt.x = vm.x - size.width;
|
||||
} else if (vm.xAlign === 'right') {
|
||||
tooltipX = vm.x + caretPadding + vm.caretSize;
|
||||
pt.x = vm.x + caretPadding + vm.caretSize;
|
||||
} else {
|
||||
tooltipX = vm.x + (tooltipTotalWidth / 2);
|
||||
pt.x = vm.x + (size.width / 2);
|
||||
}
|
||||
|
||||
// Draw Background
|
||||
return pt;
|
||||
},
|
||||
drawCaret: function drawCaret(opacity, caretPadding) {
|
||||
var vm = this._view;
|
||||
var ctx = this._chart.ctx;
|
||||
|
||||
ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(opacity).rgbString();
|
||||
ctx.beginPath();
|
||||
|
||||
if (vm.xAlign === 'left') {
|
||||
ctx.moveTo(vm.x - caretPadding, vm.y);
|
||||
ctx.lineTo(vm.x - caretPadding - vm.caretSize, vm.y - vm.caretSize);
|
||||
ctx.lineTo(vm.x - caretPadding - vm.caretSize, vm.y + vm.caretSize);
|
||||
} else {
|
||||
ctx.moveTo(vm.x + caretPadding, vm.y);
|
||||
ctx.lineTo(vm.x + caretPadding + vm.caretSize, vm.y - vm.caretSize);
|
||||
ctx.lineTo(vm.x + caretPadding + vm.caretSize, vm.y + vm.caretSize);
|
||||
}
|
||||
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
},
|
||||
drawTitle: function drawTitle(pt, vm, ctx, opacity) {
|
||||
if (vm.title.length) {
|
||||
ctx.textAlign = vm._titleAlign;
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = helpers.color(vm.titleColor).alpha(opacity).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
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
drawBody: function drawBody(pt, vm, ctx, opacity) {
|
||||
ctx.textAlign = vm._bodyAlign;
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).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";
|
||||
ctx.fillStyle = helpers.color(vm.footerColor).alpha(opacity).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;
|
||||
}, this);
|
||||
}
|
||||
},
|
||||
draw: function draw() {
|
||||
var ctx = this._chart.ctx;
|
||||
var vm = this._view;
|
||||
|
||||
if (vm.opacity === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var caretPadding = vm.caretPadding || 2;
|
||||
var tooltipSize = this.getTooltipSize();
|
||||
var backgroundWidth = tooltipSize.width;
|
||||
|
||||
// Expand to be new total size (including caret)
|
||||
tooltipSize.width += vm.caretSize + caretPadding;
|
||||
|
||||
// Smart Tooltip placement to stay on the canvas
|
||||
// Top, center, or bottom
|
||||
this.determineAlignment(tooltipSize);
|
||||
|
||||
var pt = this.getBackgroundPoint(tooltipSize, caretPadding);
|
||||
|
||||
// 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
|
||||
ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(opacity).rgbString();
|
||||
helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipHeight, vm.cornerRadius);
|
||||
helpers.drawRoundedRectangle(ctx, pt.x, pt.y, backgroundWidth, tooltipSize.height, vm.cornerRadius);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
|
||||
// Draw Caret
|
||||
if (this._options.tooltips.enabled) {
|
||||
ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(opacity).rgbString();
|
||||
|
||||
if (vm.xAlign === 'left') {
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(vm.x - caretPadding, vm.y);
|
||||
ctx.lineTo(vm.x - caretPadding - vm.caretSize, vm.y - vm.caretSize);
|
||||
ctx.lineTo(vm.x - caretPadding - vm.caretSize, vm.y + vm.caretSize);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
} else {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(vm.x + caretPadding, vm.y);
|
||||
ctx.lineTo(vm.x + caretPadding + vm.caretSize, vm.y - vm.caretSize);
|
||||
ctx.lineTo(vm.x + caretPadding + vm.caretSize, vm.y + vm.caretSize);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
|
||||
// Draw Title, Body, and Footer
|
||||
|
||||
if (this._options.tooltips.enabled) {
|
||||
|
||||
var yBase = tooltipY + vm.yPadding;
|
||||
var xBase = tooltipX + vm.xPadding;
|
||||
// Draw Caret
|
||||
this.drawCaret(opacity, caretPadding);
|
||||
|
||||
// Draw Title, Body, and Footer
|
||||
pt.x += vm.xPadding;
|
||||
pt.y += vm.yPadding;
|
||||
|
||||
// Titles
|
||||
|
||||
if (vm.title.length) {
|
||||
ctx.textAlign = vm._titleAlign;
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = helpers.color(vm.titleColor).alpha(opacity).rgbString();
|
||||
ctx.font = helpers.fontString(vm.titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
|
||||
|
||||
helpers.each(vm.title, function(title, i) {
|
||||
ctx.fillText(title, xBase, yBase);
|
||||
yBase += vm.titleFontSize + vm.titleSpacing; // Line Height and spacing
|
||||
if (i + 1 === vm.title.length) {
|
||||
yBase += vm.titleMarginBottom - vm.titleSpacing; // If Last, add margin, remove spacing
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
this.drawTitle(pt, vm, ctx, opacity);
|
||||
|
||||
// Body
|
||||
ctx.textAlign = vm._bodyAlign;
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = helpers.color(vm.bodyColor).alpha(opacity).rgbString();
|
||||
ctx.font = helpers.fontString(vm.bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
|
||||
|
||||
// Before Body
|
||||
helpers.each(vm.beforeBody, function(beforeBody) {
|
||||
ctx.fillText(beforeBody, xBase, yBase);
|
||||
yBase += 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(xBase, yBase, vm.bodyFontSize, vm.bodyFontSize);
|
||||
|
||||
// Border
|
||||
ctx.strokeStyle = helpers.color(vm.labelColors[i].borderColor).alpha(opacity).rgbaString();
|
||||
ctx.strokeRect(xBase, yBase, vm.bodyFontSize, vm.bodyFontSize);
|
||||
|
||||
// Inner square
|
||||
ctx.fillStyle = helpers.color(vm.labelColors[i].backgroundColor).alpha(opacity).rgbaString();
|
||||
ctx.fillRect(xBase + 1, yBase + 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, xBase + (this._options.tooltips.mode !== 'single' ? (vm.bodyFontSize + 2) : 0), yBase);
|
||||
|
||||
yBase += vm.bodyFontSize + vm.bodySpacing;
|
||||
|
||||
}, this);
|
||||
|
||||
// After Body
|
||||
helpers.each(vm.afterBody, function(afterBody) {
|
||||
ctx.fillText(afterBody, xBase, yBase);
|
||||
yBase += vm.bodyFontSize;
|
||||
});
|
||||
|
||||
yBase -= vm.bodySpacing; // Remove last body spacing
|
||||
|
||||
this.drawBody(pt, vm, ctx, opacity);
|
||||
|
||||
// Footer
|
||||
if (vm.footer.length) {
|
||||
|
||||
yBase += vm.footerMarginTop;
|
||||
|
||||
ctx.textAlign = vm._footerAlign;
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = helpers.color(vm.footerColor).alpha(opacity).rgbString();
|
||||
ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
|
||||
|
||||
helpers.each(vm.footer, function(footer) {
|
||||
ctx.fillText(footer, xBase, yBase);
|
||||
yBase += vm.footerFontSize + vm.footerSpacing;
|
||||
}, this);
|
||||
}
|
||||
|
||||
this.drawFooter(pt, vm, ctx, opacity);
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
}).call(this);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user