From 6869a41d40c1ff56da0e3afb78d261203494d869 Mon Sep 17 00:00:00 2001 From: Dan Manastireanu <498419+danmana@users.noreply.github.com> Date: Thu, 29 Oct 2020 22:55:40 +0200 Subject: [PATCH] feat: Draw tooltips with point styles. Closes #7774 (#7972) * feat: Draw tooltips with point styles. Closes #7774 * chore: Add tooltip usePointStyle docs * chore: Add tests and visual tests for tooltip usePointStyle * chore: Update typescript with tooltip usePointStyle --- docs/docs/configuration/tooltip.md | 26 +++ samples/samples.js | 3 + samples/tooltips/point-style.html | 193 +++++++++++++++++++++ src/plugins/plugin.tooltip.js | 61 +++++-- test/fixtures/core.tooltip/point-style.js | 73 ++++++++ test/fixtures/core.tooltip/point-style.png | Bin 0 -> 17189 bytes test/specs/plugin.tooltip.tests.js | 13 ++ types/plugins/index.d.ts | 7 + 8 files changed, 364 insertions(+), 12 deletions(-) create mode 100644 samples/tooltips/point-style.html create mode 100644 test/fixtures/core.tooltip/point-style.js create mode 100644 test/fixtures/core.tooltip/point-style.png diff --git a/docs/docs/configuration/tooltip.md b/docs/docs/configuration/tooltip.md index 56f1bbe6771..aef329af802 100644 --- a/docs/docs/configuration/tooltip.md +++ b/docs/docs/configuration/tooltip.md @@ -37,6 +37,7 @@ The tooltip configuration is passed into the `options.tooltips` namespace. The g | `displayColors` | `boolean` | `true` | If true, color boxes are shown in the tooltip. | `boxWidth` | `number` | `bodyFont.size` | Width of the color box if displayColors is true. | `boxHeight` | `number` | `bodyFont.size` | Height of the color box if displayColors is true. +| `usePointStyle` | `boolean` | `false` | Use the corresponding point style (from dataset options) instead of color boxes, ex: star, triangle etc. (size is based on the minimum value between boxWidth and boxHeight). | `borderColor` | `Color` | `'rgba(0, 0, 0, 0)'` | Color of the border. | `borderWidth` | `number` | `0` | Size of the border. | `rtl` | `boolean` | | `true` for rendering the legends from right to left. @@ -111,6 +112,7 @@ All functions are called with the same arguments: a [tooltip item context](#tool | `label` | `TooltipItem, object` | Returns text to render for an individual item in the tooltip. [more...](#label-callback) | `labelColor` | `TooltipItem, Chart` | Returns the colors to render for the tooltip item. [more...](#label-color-callback) | `labelTextColor` | `TooltipItem, Chart` | Returns the colors for the text of the label for the tooltip item. +| `labelPointStyle` | `TooltipItem, Chart` | Returns the point style to use instead of color boxes if usePointStyle is true (object with values `pointStyle` and `rotation`). Default implementation uses the point style from the dataset points. [more...](#label-point-style-callback) | `afterLabel` | `TooltipItem, object` | Returns text to render after an individual label. | `afterBody` | `TooltipItem[], object` | Returns text to render after the body section. | `beforeFooter` | `TooltipItem[], object` | Returns text to render before the footer section. @@ -171,6 +173,30 @@ var chart = new Chart(ctx, { }); ``` +### Label Point Style Callback + +For example, to draw triangles instead of the regular color box for each item in the tooltip you could do: + +```javascript +var chart = new Chart(ctx, { + type: 'line', + data: data, + options: { + tooltips: { + usePointStyle: true, + callbacks: { + labelPointStyle: function(context) { + return { + pointStyle: 'triangle', + rotation: 0 + }; + } + } + } + } +}); +``` + ### Tooltip Item Context diff --git a/samples/samples.js b/samples/samples.js index eb569df52e7..a5025f1f09b 100644 --- a/samples/samples.js +++ b/samples/samples.js @@ -205,6 +205,9 @@ }, { title: 'Border', path: 'tooltips/border.html' + }, { + title: 'Point style', + path: 'tooltips/point-style.html' }, { title: 'HTML tooltips (line)', path: 'tooltips/custom-line.html' diff --git a/samples/tooltips/point-style.html b/samples/tooltips/point-style.html new file mode 100644 index 00000000000..6ae1376c4d0 --- /dev/null +++ b/samples/tooltips/point-style.html @@ -0,0 +1,193 @@ + + + + + Tooltip Point Style + + + + + + +
+ +
+
+
+ + + + + + + + + diff --git a/src/plugins/plugin.tooltip.js b/src/plugins/plugin.tooltip.js index d9da0d8098e..eac3e5123b5 100644 --- a/src/plugins/plugin.tooltip.js +++ b/src/plugins/plugin.tooltip.js @@ -5,6 +5,7 @@ import {valueOrDefault, each, noop, isNullOrUndef, isArray, _elementsEqual, merg import {getRtlAdapter, overrideTextDirection, restoreTextDirection} from '../helpers/helpers.rtl'; import {distanceBetweenPoints} from '../helpers/helpers.math'; import {toFont} from '../helpers/helpers.options'; +import {drawPoint} from '../helpers'; /** * @typedef { import("../platform/platform.base").IEvent } IEvent @@ -382,6 +383,7 @@ export class Tooltip extends Element { this.caretX = undefined; this.caretY = undefined; this.labelColors = undefined; + this.labelPointStyles = undefined; this.labelTextColors = undefined; this.initialize(); @@ -485,6 +487,7 @@ export class Tooltip extends Element { const options = me.options; const data = me._chart.data; const labelColors = []; + const labelPointStyles = []; const labelTextColors = []; let tooltipItems = []; let i, len; @@ -506,10 +509,12 @@ export class Tooltip extends Element { // Determine colors for boxes each(tooltipItems, (context) => { labelColors.push(options.callbacks.labelColor.call(me, context)); + labelPointStyles.push(options.callbacks.labelPointStyle.call(me, context)); labelTextColors.push(options.callbacks.labelTextColor.call(me, context)); }); me.labelColors = labelColors; + me.labelPointStyles = labelPointStyles; me.labelTextColors = labelTextColors; me.dataPoints = tooltipItems; return tooltipItems; @@ -668,24 +673,48 @@ export class Tooltip extends Element { const me = this; const options = me.options; const labelColors = me.labelColors[i]; + const labelPointStyle = me.labelPointStyles[i]; const {boxHeight, boxWidth, bodyFont} = options; const colorX = getAlignedX(me, 'left'); const rtlColorX = rtlHelper.x(colorX); const yOffSet = boxHeight < bodyFont.size ? (bodyFont.size - boxHeight) / 2 : 0; const colorY = pt.y + yOffSet; - // 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.strokeStyle = labelColors.borderColor; - ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, boxWidth), colorY, boxWidth, boxHeight); - - // Inner square - ctx.fillStyle = labelColors.backgroundColor; - ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2), colorY + 1, boxWidth - 2, boxHeight - 2); + if (options.usePointStyle) { + const drawOptions = { + radius: Math.min(boxWidth, boxHeight) / 2, // fit the circle in the box + pointStyle: labelPointStyle.pointStyle, + rotation: labelPointStyle.rotation, + borderWidth: 1 + }; + // Recalculate x and y for drawPoint() because its expecting + // x and y to be center of figure (instead of top left) + const centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2; + const centerY = colorY + boxHeight / 2; + + // Fill the point with white so that colours merge nicely if the opacity is < 1 + ctx.strokeStyle = options.multiKeyBackground; + ctx.fillStyle = options.multiKeyBackground; + drawPoint(ctx, drawOptions, centerX, centerY); + + // Draw the point + ctx.strokeStyle = labelColors.borderColor; + 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.strokeStyle = labelColors.borderColor; + ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, boxWidth), colorY, boxWidth, boxHeight); + + // Inner square + ctx.fillStyle = labelColors.backgroundColor; + ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2), colorY + 1, boxWidth - 2, boxHeight - 2); + } // restore fillStyle ctx.fillStyle = me.labelTextColors[i]; @@ -1155,6 +1184,14 @@ export default { labelTextColor() { return this.options.bodyFont.color; }, + labelPointStyle(tooltipItem) { + const meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex); + const options = meta.controller.getStyle(tooltipItem.dataIndex); + return { + pointStyle: options.pointStyle, + rotation: options.rotation, + }; + }, afterLabel: noop, // Args are: (tooltipItems, data) diff --git a/test/fixtures/core.tooltip/point-style.js b/test/fixtures/core.tooltip/point-style.js new file mode 100644 index 00000000000..2579f395948 --- /dev/null +++ b/test/fixtures/core.tooltip/point-style.js @@ -0,0 +1,73 @@ +const pointStyles = ['circle', 'cross', 'crossRot', 'dash', 'line', 'rect', 'rectRounded', 'rectRot', 'star', 'triangle']; + +function newDataset(pointStyle, i) { + return { + label: '', + data: pointStyles.map(() => i), + pointStyle: pointStyle, + pointBackgroundColor: '#0000ff', + pointBorderColor: '#00ff00', + showLine: false + }; +} +module.exports = { + config: { + type: 'line', + data: { + datasets: pointStyles.map((pointStyle, i) => newDataset(pointStyle, i)), + labels: pointStyles.map(() => '') + }, + options: { + legend: false, + title: false, + scales: { + x: {display: false}, + y: {display: false} + }, + elements: { + line: { + fill: false + } + }, + tooltips: { + mode: 'nearest', + intersect: false, + usePointStyle: true, + callbacks: { + label: function() { + return '\u200b'; + } + } + }, + layout: { + padding: 15 + } + }, + plugins: [{ + afterDraw: function(chart) { + var canvas = chart.canvas; + var rect = canvas.getBoundingClientRect(); + var point, event; + + for (var i = 0; i < pointStyles.length; ++i) { + point = chart.getDatasetMeta(i).data[i]; + 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 + } + } +}; diff --git a/test/fixtures/core.tooltip/point-style.png b/test/fixtures/core.tooltip/point-style.png new file mode 100644 index 0000000000000000000000000000000000000000..defb03359fc761e3f202676fb0c87f54c881f4c6 GIT binary patch literal 17189 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 Bdo2I} literal 0 HcmV?d00001 diff --git a/test/specs/plugin.tooltip.tests.js b/test/specs/plugin.tooltip.tests.js index 2e5f3cf8ea5..b3ba80fc1b3 100644 --- a/test/specs/plugin.tooltip.tests.js +++ b/test/specs/plugin.tooltip.tests.js @@ -369,6 +369,12 @@ describe('Plugin.Tooltip', function() { }, labelTextColor: function() { return 'labelTextColor'; + }, + labelPointStyle: function() { + return { + pointStyle: 'labelPointStyle', + rotation: 42 + }; } } } @@ -459,6 +465,13 @@ describe('Plugin.Tooltip', function() { }, { borderColor: defaults.color, backgroundColor: defaults.color + }], + labelPointStyles: [{ + pointStyle: 'labelPointStyle', + rotation: 42 + }, { + pointStyle: 'labelPointStyle', + rotation: 42 }] })); diff --git a/types/plugins/index.d.ts b/types/plugins/index.d.ts index e5855fe880f..514ff06a8de 100644 --- a/types/plugins/index.d.ts +++ b/types/plugins/index.d.ts @@ -281,6 +281,7 @@ export interface TooltipModel { // colors to render for each item in body[]. This is the color of the squares in the tooltip labelColors: Color[]; labelTextColors: Color[]; + labelPointStyles: { pointStyle: PointStyle; rotation: number }[]; // 0 opacity is a hidden tooltip opacity: number; @@ -312,6 +313,7 @@ export interface ITooltipCallbacks { labelColor(this: TooltipModel, tooltipItem: ITooltipItem): { borderColor: Color; backgroundColor: Color }; labelTextColor(this: TooltipModel, tooltipItem: ITooltipItem): Color; + labelPointStyle(this: TooltipModel, tooltipItem: ITooltipItem): { pointStyle: PointStyle; rotation: number }; beforeFooter(this: TooltipModel, tooltipItems: ITooltipItem[]): string | string[]; footer(this: TooltipModel, tooltipItems: ITooltipItem[]): string | string[]; @@ -473,6 +475,11 @@ export interface ITooltipOptions extends IHoverInteractionOptions { * @default bodyFont.size */ boxHeight: number; + /** + * Use the corresponding point style (from dataset options) instead of color boxes, ex: star, triangle etc. (size is based on the minimum value between boxWidth and boxHeight) + * @default false + */ + usePointStyle: boolean; /** * Color of the border. * @default 'rgba(0, 0, 0, 0)'