From f95785ae818070651c264cb84d7ae04b6e31405c Mon Sep 17 00:00:00 2001 From: Akihiko Kusanagi Date: Wed, 28 Nov 2018 15:07:00 +0800 Subject: [PATCH] Refactor tooltip mergeOpacity calls and support CanvasPattern and CanvasGradient --- src/core/core.tooltip.js | 47 ++++++----- test/fixtures/core.tooltip/opacity.js | 104 +++++++++++++++++++++++++ test/fixtures/core.tooltip/opacity.png | Bin 0 -> 11800 bytes test/specs/core.tooltip.tests.js | 2 + 4 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 test/fixtures/core.tooltip/opacity.js create mode 100644 test/fixtures/core.tooltip/opacity.png diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index c529812cc61..c3245d9cf1e 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -168,14 +168,6 @@ var positioners = { } }; -/** - * Helper method to merge the opacity into a color - */ -function mergeOpacity(colorString, opacity) { - var color = helpers.color(colorString); - return color.alpha(opacity * color.alpha()).rgbaString(); -} - // Helper to push or concat based on if the 2nd parameter is an array or not function pushOrConcat(base, toPush) { if (toPush) { @@ -734,7 +726,7 @@ var exports = module.exports = Element.extend({ return {x1: x1, x2: x2, x3: x3, y1: y1, y2: y2, y3: y3}; }, - drawTitle: function(pt, vm, ctx, opacity) { + drawTitle: function(pt, vm, ctx) { var title = vm.title; if (title.length) { @@ -744,7 +736,7 @@ var exports = module.exports = Element.extend({ var titleFontSize = vm.titleFontSize; var titleSpacing = vm.titleSpacing; - ctx.fillStyle = mergeOpacity(vm.titleFontColor, opacity); + ctx.fillStyle = vm.titleFontColor; ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily); var i, len; @@ -759,7 +751,7 @@ var exports = module.exports = Element.extend({ } }, - drawBody: function(pt, vm, ctx, opacity) { + drawBody: function(pt, vm, ctx) { var bodyFontSize = vm.bodyFontSize; var bodySpacing = vm.bodySpacing; var body = vm.body; @@ -776,7 +768,7 @@ var exports = module.exports = Element.extend({ }; // Before body lines - ctx.fillStyle = mergeOpacity(vm.bodyFontColor, opacity); + ctx.fillStyle = vm.bodyFontColor; helpers.each(vm.beforeBody, fillLineOfText); var drawColorBoxes = vm.displayColors; @@ -784,7 +776,7 @@ var exports = module.exports = Element.extend({ // Draw body lines now helpers.each(body, function(bodyItem, i) { - var textColor = mergeOpacity(vm.labelTextColors[i], opacity); + var textColor = vm.labelTextColors[i]; ctx.fillStyle = textColor; helpers.each(bodyItem.before, fillLineOfText); @@ -792,16 +784,16 @@ var exports = module.exports = Element.extend({ // Draw Legend-like boxes if needed if (drawColorBoxes) { // Fill a white rect so that colours merge nicely if the opacity is < 1 - ctx.fillStyle = mergeOpacity(vm.legendColorBackground, opacity); + ctx.fillStyle = vm.legendColorBackground; ctx.fillRect(pt.x, pt.y, bodyFontSize, bodyFontSize); // Border ctx.lineWidth = 1; - ctx.strokeStyle = mergeOpacity(vm.labelColors[i].borderColor, opacity); + ctx.strokeStyle = vm.labelColors[i].borderColor; ctx.strokeRect(pt.x, pt.y, bodyFontSize, bodyFontSize); // Inner square - ctx.fillStyle = mergeOpacity(vm.labelColors[i].backgroundColor, opacity); + ctx.fillStyle = vm.labelColors[i].backgroundColor; ctx.fillRect(pt.x + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2); ctx.fillStyle = textColor; } @@ -820,7 +812,7 @@ var exports = module.exports = Element.extend({ pt.y -= bodySpacing; // Remove last body spacing }, - drawFooter: function(pt, vm, ctx, opacity) { + drawFooter: function(pt, vm, ctx) { var footer = vm.footer; if (footer.length) { @@ -829,7 +821,7 @@ var exports = module.exports = Element.extend({ ctx.textAlign = vm._footerAlign; ctx.textBaseline = 'top'; - ctx.fillStyle = mergeOpacity(vm.footerFontColor, opacity); + ctx.fillStyle = vm.footerFontColor; ctx.font = helpers.fontString(vm.footerFontSize, vm._footerFontStyle, vm._footerFontFamily); helpers.each(footer, function(line) { @@ -839,9 +831,9 @@ var exports = module.exports = Element.extend({ } }, - drawBackground: function(pt, vm, ctx, tooltipSize, opacity) { - ctx.fillStyle = mergeOpacity(vm.backgroundColor, opacity); - ctx.strokeStyle = mergeOpacity(vm.borderColor, opacity); + drawBackground: function(pt, vm, ctx, tooltipSize) { + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; ctx.lineWidth = vm.borderWidth; var xAlign = vm.xAlign; var yAlign = vm.yAlign; @@ -906,21 +898,26 @@ var exports = module.exports = Element.extend({ var hasTooltipContent = vm.title.length || vm.beforeBody.length || vm.body.length || vm.afterBody.length || vm.footer.length; if (this._options.enabled && hasTooltipContent) { + ctx.save(); + ctx.globalAlpha = opacity; + // Draw Background - this.drawBackground(pt, vm, ctx, tooltipSize, opacity); + this.drawBackground(pt, vm, ctx, tooltipSize); // Draw Title, Body, and Footer pt.x += vm.xPadding; pt.y += vm.yPadding; // Titles - this.drawTitle(pt, vm, ctx, opacity); + this.drawTitle(pt, vm, ctx); // Body - this.drawBody(pt, vm, ctx, opacity); + this.drawBody(pt, vm, ctx); // Footer - this.drawFooter(pt, vm, ctx, opacity); + this.drawFooter(pt, vm, ctx); + + ctx.restore(); } }, diff --git a/test/fixtures/core.tooltip/opacity.js b/test/fixtures/core.tooltip/opacity.js new file mode 100644 index 00000000000..8b872e75d73 --- /dev/null +++ b/test/fixtures/core.tooltip/opacity.js @@ -0,0 +1,104 @@ +var patternCanvas = document.createElement('canvas'); +var patternContext = patternCanvas.getContext('2d'); + +patternCanvas.width = 6; +patternCanvas.height = 6; +patternContext.fillStyle = '#ff0000'; +patternContext.fillRect(0, 0, 6, 6); +patternContext.fillStyle = '#ffff00'; +patternContext.fillRect(0, 0, 4, 4); + +var pattern = patternContext.createPattern(patternCanvas, 'repeat'); + +var gradient; + +module.exports = { + config: { + type: 'line', + data: { + datasets: [{ + data: [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + pointBorderColor: '#ff0000', + pointBackgroundColor: '#00ff00', + showLine: false + }, { + label: '', + data: [4, 4, 4, 4, 4, 5, 3, 4, 4, 4, 4], + pointBorderColor: pattern, + pointBackgroundColor: pattern, + showLine: false + }, { + label: '', + data: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + showLine: false + }], + labels: ['', '', '', '', '', '', '', '', '', '', ''] + }, + options: { + legend: false, + title: false, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + }, + elements: { + line: { + fill: false + } + }, + tooltips: { + mode: 'nearest', + intersect: false, + callbacks: { + label: function() { + return '\u200b'; + } + } + }, + layout: { + padding: 15 + } + }, + plugins: [{ + beforeDatasetsUpdate: function(chart) { + if (!gradient) { + gradient = chart.ctx.createLinearGradient(0, 0, 512, 256); + gradient.addColorStop(0, '#ff0000'); + gradient.addColorStop(1, '#0000ff'); + } + chart.config.data.datasets[2].pointBorderColor = gradient; + chart.config.data.datasets[2].pointBackgroundColor = gradient; + + return true; + }, + afterDraw: function(chart) { + var canvas = chart.canvas; + var rect = canvas.getBoundingClientRect(); + var point, event; + + for (var i = 0; i < 3; ++i) { + for (var j = 0; j < 11; ++j) { + point = chart.getDatasetMeta(i).data[j]; + event = { + type: 'mousemove', + target: canvas, + clientX: rect.left + point._model.x, + clientY: rect.top + point._model.y + }; + chart.handleEvent(event); + chart.tooltip.handleEvent(event); + chart.tooltip.transition(1); + chart.tooltip._view.opacity = j / 10; + chart.tooltip.draw(); + } + } + } + }] + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/core.tooltip/opacity.png b/test/fixtures/core.tooltip/opacity.png new file mode 100644 index 0000000000000000000000000000000000000000..142dcc05f60fb6f966594da191fb7c8b109c23ea GIT binary patch literal 11800 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