如何将径向 CanvasGradient 与 Chart.js 圆环图一起使用?

Posted

技术标签:

【中文标题】如何将径向 CanvasGradient 与 Chart.js 圆环图一起使用?【英文标题】:How to use radial CanvasGradients with Chart.js's doughnut chart? 【发布时间】:2021-02-23 00:22:21 【问题描述】:

我正在尝试创建两个径向渐变以与 Charts.js doughnut chart 一起使用。

渐变应该看起来有点像下图,但是是红色的。

使用 vanilla javascript 和 DOM 创建渐变 (createRadialGradient) 非常简单,如下面的代码 sn-p 所示:

'use strict'

const red = "hsla(1, 73.7%, 38.8%, 1)"
const redDark = "hsla(1, 60%, 30%, 1)"
const redDarker = "hsla(1, 20%, 20%, 1)"
const redLight = "hsla(1, 73.7%, 48%, 1)"

const canvasList = document.querySelectorAll('canvas.vanilla')

var ctx, gradient = createGradient1(canvasList[0].getContext('2d'))
ctx.fillStyle = gradient
drawRect(ctx)

var ctx, gradient = createGradient1(canvasList[1].getContext('2d'))
ctx.strokeStyle = gradient
ctx.lineWidth = 42
drawArc(ctx)

var ctx, gradient = createGradient2(canvasList[2].getContext('2d'))
ctx.fillStyle = gradient
drawRect(ctx)

var ctx, gradient = createGradient2(canvasList[3].getContext('2d'))
ctx.strokeStyle = gradient
ctx.lineWidth = 42
drawArc(ctx)

function createGradient1 (ctx) 
    // The inner circle is at x=110, y=90, with radius=30
    // The outer circle is at x=100, y=100, with radius=70
    // ctx.createRadialGradient(x0, y0, r0, x1, y1, r1)
    const gradient = ctx.createRadialGradient(100,100,31, 100,100,70);

    // Add three color stops
    const innerColor = redDark
    const mainColor = red
    const outerColor = redLight
    gradient.addColorStop(0, innerColor);
    gradient.addColorStop(.04, innerColor);
    gradient.addColorStop(.05, mainColor);
    gradient.addColorStop(1, outerColor);

    return  ctx, gradient 

function createGradient2 (ctx) 
    // The inner circle is at x=110, y=90, with radius=30
    // The outer circle is at x=100, y=100, with radius=70
    // ctx.createRadialGradient(x0, y0, r0, x1, y1, r1)
    const gradient = ctx.createRadialGradient(100,100,31, 100,100,70);

    // Add three color stops
    const innerColor = "hsla(1, 90%, 10%, 1)"
    const mainColor = "hsla(1, 73.7%, 20%, 1)"
    const outerColor = "transparent"
    gradient.addColorStop(0, innerColor);
    gradient.addColorStop(.04, innerColor);
    gradient.addColorStop(.05, mainColor);
    gradient.addColorStop(.7, mainColor);
    gradient.addColorStop(.73, outerColor);

    return  ctx, gradient 


function drawRect (ctx) 
    // ctx.fillRect(x, y, width, height)
    ctx.fillRect(20, 20, 160, 160);


function drawArc (ctx) 
    ctx.beginPath();
    // ctx.arc(x, y, radius, startAngle, endAngle [, anticlockwise])
    ctx.arc(100, 100, 50, 0, 2 * Math.PI);
    ctx.stroke()
.vanilla 
    display: inline-block;
<canvas class="vanilla"  ></canvas>
<canvas class="vanilla"  ></canvas>
<canvas class="vanilla"  ></canvas>
<canvas class="vanilla"  ></canvas>

但是当我将相同的 2 个渐变应用到 Charts.js 时,我得到了一个灰色的甜甜圈。使用单个渐变不会改变结果。但是,使用两种 Hsla 颜色可以按预期工作 (red & "white")。

/** @type CanvasRenderingContext2D */
const ctx = document.querySelector('.d-goal--canvas').getContext('2d')
const red = "hsla(1, 73.7%, 38.8%, 1)"
const  gradient1  = createGradient1(ctx)
const  gradient2  = createGradient2(ctx)
const donut = new Chart(ctx, 
    type: 'doughnut',
    data: 
        labels: [
            "Pledged",
            "Missing"
        ],
        datasets: [
            label: "Donations",
            data: [420, 80],
            cubicInterpolationMode: "monotone",
            // borderColor: [red, "white"],
            // backgroundColor: [red, "white"],
            borderColor: [gradient1, gradient2],
            backgroundColor: [gradient1, gradient2],
        ]
    ,
    options: 
        legend: 
            display: false
        
    
)

function createGradient1 (ctx) 
    // The inner circle is at x=110, y=90, with radius=30
    // The outer circle is at x=100, y=100, with radius=70
    // ctx.createRadialGradient(x0, y0, r0, x1, y1, r1)
    const gradient = ctx.createRadialGradient(100,100,31, 100,100,70);

    // Add three color stops
    const innerColor = "hsla(1, 60%, 30%, 1)"
    const mainColor = red
    const outerColor = "hsla(1, 73.7%, 48%, 1)"
    gradient.addColorStop(0, innerColor);
    gradient.addColorStop(.04, innerColor);
    gradient.addColorStop(.05, mainColor);
    gradient.addColorStop(1, outerColor);

    return  ctx, gradient 

function createGradient2 (ctx) 
    // The inner circle is at x=110, y=90, with radius=30
    // The outer circle is at x=100, y=100, with radius=70
    // ctx.createRadialGradient(x0, y0, r0, x1, y1, r1)
    const gradient = ctx.createRadialGradient(100,100,31, 100,100,70);

    // Add three color stops
    const innerColor = "hsla(1, 90%, 10%, 1)"
    const mainColor = "hsla(1, 73.7%, 20%, 1)"
    const outerColor = "transparent"
    gradient.addColorStop(0, innerColor);
    gradient.addColorStop(.04, innerColor);
    gradient.addColorStop(.05, mainColor);
    gradient.addColorStop(.7, mainColor);
    gradient.addColorStop(.73, outerColor);

    return  ctx, gradient 
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas class="d-goal--canvas"></canvas>

我做错了什么?

我阅读了Jelena Jovanovic's gradient tutorial,这是一个应用于折线图的线性渐变 (createLinearGradient),我看不出我在做什么不同。也许我的渐变区域是错误的,但据我所知,这不应该导致灰色图表。

The Charts.js documentation has a section about colors 在那里他们描述了如何使用CanvasGradient 但仅限于createLinearGradient,这让我认为这可能是 Charts.js 中的一个错误......

【问题讨论】:

糟糕!我想我发现了我的错误。 createGradient1createGradient2 返回 ctx, gradient 而不是 gradient1gradient2!我的解构变量应该是const gradient: gradient1 = createGradient1(ctx)const gradient: gradient2 = createGradient2(ctx) 那解决了您的问题吗? 只是问题的一部分。我现在得到第一个渐变,但不是第二个。而且看起来不对,这可能是因为渐变区域与圆环图区域不匹配。当我有时间进一步调查时,我会发布一个解决方案。 【参考方案1】:

问题在于渐变填充样式实际应用于您的甜甜圈的方式。您的 - 和我最初的 - 假设是 chart.js 将负责定位并将渐变缩放到适当的大小以填充甜甜圈。嗯,不是的情况。相反,它使用画布上渐变的大小和位置。

为了更好地理解,让我们看一下您的其中一种渐变的代码:

const red = "hsla(1, 73.7%, 38.8%, 1)"
const gradient = ctx.createRadialGradient(100,100,31, 100,100,70);
const innerColor = "hsla(1, 60%, 30%, 1)"
const mainColor = red
const outerColor = "hsla(1, 73.7%, 48%, 1)"
gradient.addColorStop(0, innerColor);
gradient.addColorStop(.04, innerColor);
gradient.addColorStop(.05, mainColor);
gradient.addColorStop(1, outerColor);

这将在 x=100 和 y=100 处产生一个直径为 140 像素的渐变,例如:

现在,如果我们进一步挖掘并假设您绘制的实际画布的大小是 797 x 419 像素,我们可以看到问题:

渐变完全超出了甜甜圈的形状!

要修复它,渐变需要位于甜甜圈的中心并具有适当的大小以完全填充它。有点像这样:

这说起来容易做起来难,因为最初我们不知道画布的确切大小,因为 chart.js 会自动拉伸它以填充浏览器窗口。

所以我们可以做些什么来解决这个问题:

使用 chart.js 创建甜甜圈,但不要填充它 等到 chart.js 触发 resize 事件以获取画布的实际大小 根据画布的大小计算渐变的尺寸并将其绘制在中心 最后用渐变填充甜甜圈的背景颜色

这是一个示例(请以“整页”运行,因为我们在 *** 的迷你预览框架中没有获得正确的窗口大小):

const canvas = document.querySelector('.d-goal--canvas');
const ctx = canvas.getContext('2d')
const red = "hsla(1, 73.7%, 38.8%, 1)"
let gradient1;
let gradient2;

function createGradient1(ctx) 
  const gradient = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, canvas.height / 4, canvas.width / 2, canvas.height / 2, canvas.height / 2);

  const innerColor = "hsla(1, 60%, 30%, 1)"
  const mainColor = red
  const outerColor = "hsla(1, 73.7%, 48%, 1)"
  gradient.addColorStop(0, innerColor);
  gradient.addColorStop(.12, innerColor);
  gradient.addColorStop(.121, mainColor);
  gradient.addColorStop(1, outerColor);

  return gradient;


function createGradient2(ctx) 
  const gradient = ctx.createRadialGradient(canvas.width / 2, canvas.height / 2, canvas.height / 4, canvas.width / 2, canvas.height / 2, canvas.height / 2);

  const innerColor = "hsla(1, 90%, 10%, 1)"
  const mainColor = "hsla(1, 73.7%, 20%, 1)"
  const outerColor = "transparent"
  gradient.addColorStop(0, innerColor);
  gradient.addColorStop(.12, innerColor);
  gradient.addColorStop(.121, mainColor);
  gradient.addColorStop(.99, mainColor);
  gradient.addColorStop(1, outerColor);

  return gradient;


function resized() 
  gradient1 = createGradient1(ctx);
  gradient2 = createGradient2(ctx);
  config.data.datasets[0].backgroundColor = [gradient1, gradient2];
  donut.update();

var config = 
  type: 'doughnut',
  data: 
    labels: [
      "Pledged",
      "Missing"
    ],
    datasets: [
      label: "Donations",
      data: [420, 80],
      cubicInterpolationMode: "monotone"
    ]
  ,
  options: 
    onResize: resized,
    legend: 
      display: false
    
  
;
const donut = new Chart(ctx, config);
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas class="d-goal--canvas"></canvas>

【讨论】:

只是 真棒 @obscure ! 很高兴我能帮助@dotnetCarpenter! =)

以上是关于如何将径向 CanvasGradient 与 Chart.js 圆环图一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

canvas createRadialGradient 用法

Canvas使用渐变之-线性渐变详解

CreateJS径向渐变与矩阵

如何修复不一致的颜色到清晰的径向渐变层

如何创建径向 UIView?

颤振中的侧面径向菜单