Skip to content

Commit

Permalink
Fix title, body and footer alignment inside tooltip (chartjs#5925)
Browse files Browse the repository at this point in the history
  • Loading branch information
kurkle authored and simonbrunel committed Jan 8, 2019
1 parent eed12ae commit 31a5c36
Show file tree
Hide file tree
Showing 2 changed files with 199 additions and 11 deletions.
40 changes: 29 additions & 11 deletions src/core/core.tooltip.js
Expand Up @@ -463,6 +463,14 @@ function getBackgroundPoint(vm, size, alignment, chart) {
};
}

function getAlignedX(vm, align) {
return align === 'center'
? vm.x + vm.width / 2
: align === 'right'
? vm.x + vm.width - vm.xPadding
: vm.x + vm.xPadding;
}

/**
* Helper to build before and after body lines
*/
Expand Down Expand Up @@ -730,6 +738,8 @@ var exports = Element.extend({
var title = vm.title;

if (title.length) {
pt.x = getAlignedX(vm, vm._titleAlign);

ctx.textAlign = vm._titleAlign;
ctx.textBaseline = 'top';

Expand All @@ -754,14 +764,21 @@ var exports = Element.extend({
drawBody: function(pt, vm, ctx) {
var bodyFontSize = vm.bodyFontSize;
var bodySpacing = vm.bodySpacing;
var bodyAlign = vm._bodyAlign;
var body = vm.body;
var drawColorBoxes = vm.displayColors;
var labelColors = vm.labelColors;
var xLinePadding = 0;
var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0;
var textColor;

ctx.textAlign = vm._bodyAlign;
ctx.textAlign = bodyAlign;
ctx.textBaseline = 'top';
ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);

pt.x = getAlignedX(vm, bodyAlign);

// Before Body
var xLinePadding = 0;
var fillLineOfText = function(line) {
ctx.fillText(line, pt.x + xLinePadding, pt.y);
pt.y += bodyFontSize + bodySpacing;
Expand All @@ -771,12 +788,13 @@ var exports = Element.extend({
ctx.fillStyle = vm.bodyFontColor;
helpers.each(vm.beforeBody, fillLineOfText);

var drawColorBoxes = vm.displayColors;
xLinePadding = drawColorBoxes ? (bodyFontSize + 2) : 0;
xLinePadding = drawColorBoxes && bodyAlign !== 'right'
? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2)
: 0;

// Draw body lines now
helpers.each(body, function(bodyItem, i) {
var textColor = vm.labelTextColors[i];
textColor = vm.labelTextColors[i];
ctx.fillStyle = textColor;
helpers.each(bodyItem.before, fillLineOfText);

Expand All @@ -785,16 +803,16 @@ var exports = Element.extend({
if (drawColorBoxes) {
// Fill a white rect so that colours merge nicely if the opacity is < 1
ctx.fillStyle = vm.legendColorBackground;
ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
ctx.fillRect(colorX, pt.y, bodyFontSize, bodyFontSize);

// Border
ctx.lineWidth = 1;
ctx.strokeStyle = vm.labelColors[i].borderColor;
ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize);
ctx.strokeStyle = labelColors[i].borderColor;
ctx.strokeRect(colorX, pt.y, bodyFontSize, bodyFontSize);

// Inner square
ctx.fillStyle = vm.labelColors[i].backgroundColor;
ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
ctx.fillStyle = labelColors[i].backgroundColor;
ctx.fillRect(colorX + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
ctx.fillStyle = textColor;
}

Expand All @@ -816,6 +834,7 @@ var exports = Element.extend({
var footer = vm.footer;

if (footer.length) {
pt.x = getAlignedX(vm, vm._footerAlign);
pt.y += vm.footerMarginTop;

ctx.textAlign = vm._footerAlign;
Expand Down Expand Up @@ -905,7 +924,6 @@ var exports = Element.extend({
this.drawBackground(pt, vm, ctx, tooltipSize);

// Draw Title, Body, and Footer
pt.x += vm.xPadding;
pt.y += vm.yPadding;

// Titles
Expand Down
170 changes: 170 additions & 0 deletions test/specs/core.tooltip.tests.js
Expand Up @@ -1099,4 +1099,174 @@ describe('Core.Tooltip', function() {
}]
}));
});

describe('text align', function() {
var globalDefaults = Chart.defaults.global;
var makeView = function(title, body, footer) {
return {
// Positioning
x: 100,
y: 100,
width: 100,
height: 100,
xPadding: 5,
yPadding: 5,
xAlign: 'left',
yAlign: 'top',

// Body
bodyFontColor: '#fff',
_bodyFontFamily: globalDefaults.defaultFontFamily,
_bodyFontStyle: globalDefaults.defaultFontStyle,
_bodyAlign: body,
bodyFontSize: globalDefaults.defaultFontSize,
bodySpacing: 2,

// Title
titleFontColor: '#fff',
_titleFontFamily: globalDefaults.defaultFontFamily,
_titleFontStyle: 'bold',
titleFontSize: globalDefaults.defaultFontSize,
_titleAlign: title,
titleSpacing: 2,
titleMarginBottom: 6,

// Footer
footerFontColor: '#fff',
_footerFontFamily: globalDefaults.defaultFontFamily,
_footerFontStyle: 'bold',
footerFontSize: globalDefaults.defaultFontSize,
_footerAlign: footer,
footerSpacing: 2,
footerMarginTop: 6,

// Appearance
caretSize: 5,
cornerRadius: 6,
borderColor: '#aaa',
borderWidth: 1,
backgroundColor: 'rgba(0,0,0,0.8)',
opacity: 1,
legendColorBackground: '#fff',

// Text
title: ['title'],
beforeBody: [],
body: [{
before: [],
lines: ['label'],
after: []
}],
afterBody: [],
footer: ['footer'],
caretPadding: 2,
labelTextColors: ['#fff'],
labelColors: [{
borderColor: 'rgb(255, 0, 0)',
backgroundColor: 'rgb(0, 255, 0)'
}, {
borderColor: 'rgb(0, 0, 255)',
backgroundColor: 'rgb(0, 255, 255)'
}]
};
};
var drawBody = [
{name: 'save', args: []},
{name: 'setFillStyle', args: ['rgba(0,0,0,0.8)']},
{name: 'setStrokeStyle', args: ['#aaa']},
{name: 'setLineWidth', args: [1]},
{name: 'beginPath', args: []},
{name: 'moveTo', args: [106, 100]},
{name: 'lineTo', args: [106, 100]},
{name: 'lineTo', args: [111, 95]},
{name: 'lineTo', args: [116, 100]},
{name: 'lineTo', args: [194, 100]},
{name: 'quadraticCurveTo', args: [200, 100, 200, 106]},
{name: 'lineTo', args: [200, 194]},
{name: 'quadraticCurveTo', args: [200, 200, 194, 200]},
{name: 'lineTo', args: [106, 200]},
{name: 'quadraticCurveTo', args: [100, 200, 100, 194]},
{name: 'lineTo', args: [100, 106]},
{name: 'quadraticCurveTo', args: [100, 100, 106, 100]},
{name: 'closePath', args: []},
{name: 'fill', args: []},
{name: 'stroke', args: []}
];

var mockContext = window.createMockContext();
var tooltip = new Chart.Tooltip({
_options: globalDefaults.tooltips,
_chart: {
ctx: mockContext,
}
});

it('Should go left', function() {
mockContext.resetCalls();
tooltip._view = makeView('left', 'left', 'left');
tooltip.draw();

expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['title', 105, 105]},
{name: 'setFillStyle', args: ['#fff']},
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['label', 105, 123]},
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['footer', 105, 141]},
{name: 'restore', args: []}
]));
});

it('Should go right', function() {
mockContext.resetCalls();
tooltip._view = makeView('right', 'right', 'right');
tooltip.draw();

expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['title', 195, 105]},
{name: 'setFillStyle', args: ['#fff']},
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['label', 195, 123]},
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['footer', 195, 141]},
{name: 'restore', args: []}
]));
});

it('Should center', function() {
mockContext.resetCalls();
tooltip._view = makeView('center', 'center', 'center');
tooltip.draw();

expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['title', 150, 105]},
{name: 'setFillStyle', args: ['#fff']},
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['label', 150, 123]},
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['footer', 150, 141]},
{name: 'restore', args: []}
]));
});

it('Should allow mixed', function() {
mockContext.resetCalls();
tooltip._view = makeView('right', 'center', 'left');
tooltip.draw();

expect(mockContext.getCalls()).toEqual(Array.prototype.concat(drawBody, [
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['title', 195, 105]},
{name: 'setFillStyle', args: ['#fff']},
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['label', 150, 123]},
{name: 'setFillStyle', args: ['#fff']},
{name: 'fillText', args: ['footer', 105, 141]},
{name: 'restore', args: []}
]));
});
});
});

0 comments on commit 31a5c36

Please sign in to comment.