Skip to content

Commit

Permalink
Tooltip colorbox supports configurable borderWidth, borderRadius, and…
Browse files Browse the repository at this point in the history
… dash effect (#8874)

* Start on extending tooltip style
* Correct borderRadius implementation
* Tests of updated tooltip styling
* Update docs
  • Loading branch information
etimberg committed Apr 10, 2021
1 parent a84347b commit 7ee498e
Show file tree
Hide file tree
Showing 10 changed files with 333 additions and 63 deletions.
9 changes: 6 additions & 3 deletions docs/configuration/tooltip.md
Expand Up @@ -153,7 +153,7 @@ var chart = new Chart(ctx, {

### Label Color Callback

For example, to return a red box for each item in the tooltip you could do:
For example, to return a red box with a blue dashed border that has a border radius for each item in the tooltip you could do:

```javascript
var chart = new Chart(ctx, {
Expand All @@ -165,8 +165,11 @@ var chart = new Chart(ctx, {
callbacks: {
labelColor: function(context) {
return {
borderColor: 'rgb(255, 0, 0)',
backgroundColor: 'rgb(255, 0, 0)'
borderColor: 'rgb(0, 0, 255)',
backgroundColor: 'rgb(255, 0, 0)',
borderWidth: 2,
borderDash: [2, 2],
borderRadius: 2,
};
},
labelTextColor: function(context) {
Expand Down
35 changes: 1 addition & 34 deletions src/elements/element.bar.js
@@ -1,6 +1,6 @@
import Element from '../core/core.element';
import {addRoundedRectPath} from '../helpers/helpers.canvas';
import {toTRBL, toTRBLCorners} from '../helpers/helpers.options';
import {PI, HALF_PI} from '../helpers/helpers.math';

/**
* Helper function to get the bounds of the bar regardless of the orientation
Expand Down Expand Up @@ -141,39 +141,6 @@ function hasRadius(radius) {
return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
}

/**
* Add a path of a rectangle with rounded corners to the current sub-path
* @param {CanvasRenderingContext2D} ctx Context
* @param {*} rect Bounding rect
*/
function addRoundedRectPath(ctx, rect) {
const {x, y, w, h, radius} = rect;

// top left arc
ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, -HALF_PI, PI, true);

// line from top left to bottom left
ctx.lineTo(x, y + h - radius.bottomLeft);

// bottom left arc
ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);

// line from bottom left to bottom right
ctx.lineTo(x + w - radius.bottomRight, y + h);

// bottom right arc
ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);

// line from bottom right to top right
ctx.lineTo(x + w, y + radius.topRight);

// top right arc
ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);

// line from top right to top left
ctx.lineTo(x + radius.topLeft, y);
}

/**
* Add a path of a rectangle to the current sub-path
* @param {CanvasRenderingContext2D} ctx Context
Expand Down
33 changes: 33 additions & 0 deletions src/helpers/helpers.canvas.js
Expand Up @@ -377,3 +377,36 @@ export function renderText(ctx, text, x, y, font, opts = {}) {

ctx.restore();
}

/**
* Add a path of a rectangle with rounded corners to the current sub-path
* @param {CanvasRenderingContext2D} ctx Context
* @param {*} rect Bounding rect
*/
export function addRoundedRectPath(ctx, rect) {
const {x, y, w, h, radius} = rect;

// top left arc
ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, -HALF_PI, PI, true);

// line from top left to bottom left
ctx.lineTo(x, y + h - radius.bottomLeft);

// bottom left arc
ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);

// line from bottom left to bottom right
ctx.lineTo(x + w - radius.bottomRight, y + h);

// bottom right arc
ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);

// line from bottom right to top right
ctx.lineTo(x + w, y + radius.topRight);

// top right arc
ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);

// line from top right to top left
ctx.lineTo(x + radius.topLeft, y);
}
61 changes: 50 additions & 11 deletions src/plugins/plugin.tooltip.js
@@ -1,7 +1,8 @@
import Animations from '../core/core.animations';
import Element from '../core/core.element';
import {addRoundedRectPath} from '../helpers/helpers.canvas';
import {each, noop, isNullOrUndef, isArray, _elementsEqual} from '../helpers/helpers.core';
import {toFont, toPadding} from '../helpers/helpers.options';
import {toFont, toPadding, toTRBLCorners} from '../helpers/helpers.options';
import {getRtlAdapter, overrideTextDirection, restoreTextDirection} from '../helpers/helpers.rtl';
import {distanceBetweenPoints, _limitValue} from '../helpers/helpers.math';
import {drawPoint} from '../helpers';
Expand Down Expand Up @@ -377,6 +378,8 @@ export class Tooltip extends Element {
this.width = undefined;
this.caretX = undefined;
this.caretY = undefined;
// TODO: V4, make this private, rename to `_labelStyles`, and combine with `labelPointStyles`
// and `labelTextColors` to create a single variable
this.labelColors = undefined;
this.labelPointStyles = undefined;
this.labelTextColors = undefined;
Expand Down Expand Up @@ -709,18 +712,50 @@ export class Tooltip extends Element {
ctx.fillStyle = labelColors.backgroundColor;
drawPoint(ctx, drawOptions, centerX, centerY);
} else {
// Fill a white rect so that colours merge nicely if the opacity is < 1
ctx.fillStyle = options.multiKeyBackground;
ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, boxWidth), colorY, boxWidth, boxHeight);

// Border
ctx.lineWidth = 1;
ctx.lineWidth = labelColors.borderWidth || 1; // TODO, v4 remove fallback
ctx.strokeStyle = labelColors.borderColor;
ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, boxWidth), colorY, boxWidth, boxHeight);
ctx.setLineDash(labelColors.borderDash || []);
ctx.lineDashOffset = labelColors.borderDashOffset || 0;

// Inner square
ctx.fillStyle = labelColors.backgroundColor;
ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2), colorY + 1, boxWidth - 2, boxHeight - 2);
// Fill a white rect so that colours merge nicely if the opacity is < 1
const outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth);
const innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2);
const borderRadius = toTRBLCorners(labelColors.borderRadius);

if (Object.values(borderRadius).some(v => v !== 0)) {
ctx.beginPath();
ctx.fillStyle = options.multiKeyBackground;
addRoundedRectPath(ctx, {
x: outerX,
y: colorY,
w: boxWidth,
h: boxHeight,
radius: borderRadius,
});
ctx.fill();
ctx.stroke();

// Inner square
ctx.fillStyle = labelColors.backgroundColor;
ctx.beginPath();
addRoundedRectPath(ctx, {
x: innerX,
y: colorY + 1,
w: boxWidth - 2,
h: boxHeight - 2,
radius: borderRadius,
});
ctx.fill();
} else {
// Normal rect
ctx.fillStyle = options.multiKeyBackground;
ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
// Inner square
ctx.fillStyle = labelColors.backgroundColor;
ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
}
}

// restore fillStyle
Expand Down Expand Up @@ -1197,7 +1232,11 @@ export default {
const options = meta.controller.getStyle(tooltipItem.dataIndex);
return {
borderColor: options.borderColor,
backgroundColor: options.backgroundColor
backgroundColor: options.backgroundColor,
borderWidth: options.borderWidth,
borderDash: options.borderDash,
borderDashOffset: options.borderDashOffset,
borderRadius: 0,
};
},
labelTextColor() {
Expand Down
75 changes: 75 additions & 0 deletions test/fixtures/plugin.tooltip/color-box-border-dash.js
@@ -0,0 +1,75 @@
module.exports = {
config: {
type: 'line',
data: {
datasets: [{
data: [8, 7, 6, 5],
pointBorderColor: '#ff0000',
pointBackgroundColor: '#00ff00',
showLine: false
}],
labels: ['', '', '', '']
},
options: {
scales: {
x: {display: false},
y: {display: false}
},
elements: {
line: {
fill: false
}
},
plugins: {
legend: false,
title: false,
filler: false,
tooltip: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function() {
return '\u200b';
},
labelColor: function(tooltipItem) {
const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
const options = meta.controller.getStyle(tooltipItem.dataIndex);
return {
borderColor: options.borderColor,
backgroundColor: options.backgroundColor,
borderWidth: 2,
borderDash: [2, 2]
};
},
}
},
},

layout: {
padding: 15
}
},
plugins: [{
afterDraw: function(chart) {
const canvas = chart.canvas;
const rect = canvas.getBoundingClientRect();
const point = chart.getDatasetMeta(0).data[1];
const event = {
type: 'mousemove',
target: canvas,
clientX: rect.left + point.x,
clientY: rect.top + point.y
};
chart._handleEvent(event);
chart.tooltip.handleEvent(event);
chart.tooltip.draw(chart.ctx);
}
}]
},
options: {
canvas: {
height: 256,
width: 512
}
}
};
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 78 additions & 0 deletions test/fixtures/plugin.tooltip/color-box-border-radius.js
@@ -0,0 +1,78 @@
module.exports = {
config: {
type: 'line',
data: {
datasets: [{
data: [8, 7, 6, 5],
pointBorderColor: '#ff0000',
pointBackgroundColor: '#00ff00',
showLine: false
}],
labels: ['', '', '', '']
},
options: {
scales: {
x: {display: false},
y: {display: false}
},
elements: {
line: {
fill: false
}
},
plugins: {
legend: false,
title: false,
filler: false,
tooltip: {
mode: 'nearest',
intersect: false,
callbacks: {
label: function() {
return '\u200b';
},
labelColor: function(tooltipItem) {
const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
const options = meta.controller.getStyle(tooltipItem.dataIndex);
return {
borderColor: options.borderColor,
backgroundColor: options.backgroundColor,
borderWidth: 2,
borderRadius: {
topRight: 5,
bottomRight: 5,
},
};
},
}
},
},

layout: {
padding: 15
}
},
plugins: [{
afterDraw: function(chart) {
const canvas = chart.canvas;
const rect = canvas.getBoundingClientRect();
const point = chart.getDatasetMeta(0).data[1];
const event = {
type: 'mousemove',
target: canvas,
clientX: rect.left + point.x,
clientY: rect.top + point.y
};
chart._handleEvent(event);
chart.tooltip.handleEvent(event);
chart.tooltip.draw(chart.ctx);
}
}]
},
options: {
canvas: {
height: 256,
width: 512
}
}
};
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 7ee498e

Please sign in to comment.