### 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, {
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) {
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
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
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);
+}
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';
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;
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
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() {
--- /dev/null
+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
+ }
+ }
+};
--- /dev/null
+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
+ }
+ }
+};
footer: [],
labelColors: [{
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}, {
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}]
}));
expect(tooltip.labelColors).toEqual([{
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}]);
expect(tooltip.x).toBeCloseToPixel(267);
labelTextColors: ['labelTextColor', 'labelTextColor'],
labelColors: [{
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}, {
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}],
labelPointStyles: [{
pointStyle: 'labelPointStyle',
footer: [],
labelColors: [{
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}, {
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}]
}));
footer: [],
labelColors: [{
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}, {
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}]
}));
footer: [],
labelColors: [{
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}, {
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}]
}));
footer: [],
labelColors: [{
borderColor: defaults.borderColor,
- backgroundColor: defaults.backgroundColor
+ backgroundColor: defaults.backgroundColor,
+ borderWidth: 1,
+ borderDash: undefined,
+ borderDashOffset: undefined,
+ borderRadius: 0,
}]
}));
});
export type TooltipXAlignment = 'left' | 'center' | 'right';
export type TooltipYAlignment = 'top' | 'center' | 'bottom';
+export interface TooltipLabelStyle {
+ borderColor: Color;
+ backgroundColor: Color;
+
+ /**
+ * Width of border line
+ * @since 3.1.0
+ */
+ borderWidth?: number;
+
+ /**
+ * Border dash
+ * @since 3.1.0
+ */
+ borderDash?: [number, number];
+
+ /**
+ * Border dash offset
+ * @since 3.1.0
+ */
+ borderDashOffset?: number;
+ /**
+ * borderRadius
+ * @since 3.1.0
+ */
+ borderRadius?: number | BorderRadius;
+}
export interface TooltipModel<TType extends ChartType> {
// The items that we are rendering in the tooltip. See Tooltip Item Interface section
dataPoints: TooltipItem<TType>[];
// lines of text that form the footer
footer: string[];
- // colors to render for each item in body[]. This is the color of the squares in the tooltip
- labelColors: Color[];
+ // Styles to render for each item in body[]. This is the styling of the squares in the tooltip
+ labelColors: TooltipLabelStyle[];
labelTextColors: Color[];
labelPointStyles: { pointStyle: PointStyle; rotation: number }[];
label(this: Model, tooltipItem: Item): string | string[];
afterLabel(this: Model, tooltipItem: Item): string | string[];
- labelColor(this: Model, tooltipItem: Item): { borderColor: Color; backgroundColor: Color };
+ labelColor(this: Model, tooltipItem: Item): TooltipLabelStyle;
labelTextColor(this: Model, tooltipItem: Item): Color;
labelPointStyle(this: Model, tooltipItem: Item): { pointStyle: PointStyle; rotation: number };