具有最佳随机背景颜色的图表.js饼图

Chart.js Pie with best random background color

提问人:Jordy 提问时间:11/10/2023 更新时间:11/18/2023 访问量:199

问:

我有一些数据将以饼图形式可视化。

但是,我希望 2 种相邻颜色不相同。例如,如果我有 3 个数据,那么我不希望它们具有背景颜色 , , 并且因为颜色和对比度分数很差 根据 coolors.co#90EE90#A1FFA1#FF0000#90EE90#A1FFA1

let randomColor = () => {
  let characters='0123456789ABCDEF';
  let randomString='';
  for (let i=0; i<6; i++) {
    let randomIndex=Math.floor(Math.random()*characters.length);
    randomString+=characters.charAt(randomIndex);
  }
  return '#'+randomString;
}
let round = (num) => Math.round(num*100)/100;

let chart = null;
$('#generate').click(()=>{
  let data = [];
  let labels = [];
  let pieces = $('#pieces').val();
  for(let i=0; i<pieces; i++) {
    labels.push(randomColor());
    data.push(round(1/pieces));
  }
  if(chart) chart.destroy();
  chart = new Chart($('#chart'), {
    type: 'pie',
    data: {
      labels: labels,
      datasets: [{
        data: data,
        backgroundColor: labels,
        borderWidth: 0
      }]
    },
    options: {
      plugins: {
        tooltip: {
          callbacks: {
            label: (context) => {
              return context.parsed;
            }
          }
        }
      }
    }
  });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<div>
  <input id="pieces" type="number" value="20" />
  <button id="generate">Generate Chart</button>
</div>
<canvas id="chart"></canvas>

你能帮我生成一些随机的背景颜色,但相邻颜色有很好的对比度分数或不会伤害眼睛吗?

JavaScript 颜色 图表 .js 对比度

评论


答:

4赞 Alberto Fecchi 11/12/2023 #1

一个可能的解决方案是使用颜色对比度检查器库,如下所示: https://www.npmjs.com/package/wcag-contrast

它基于 WCAG 对比度标准:https://www.w3.org/TR/WCAG20/#contrast-ratiodef

使用该函数,您可以检查两个十六进制值之间的对比度,即介于(低对比度)和(高对比度)之间的值。hex()121

您的代码可以通过许多不同的方式进行改进,无论如何,我只添加了“核心”改进以添加所讨论的功能(以便您可以更好地理解我所做的工作)

const CONTRAST_THRESHOLD = 6;

let randomColor = (prevColor = '#fff') => {
    const characters='0123456789ABCDEF';
    let currentColor;
    do {
        currentColor = '';
        for (let i = 0; i < 6; i++) {
            let randomIndex=Math.floor(Math.random()*characters.length);
            currentColor+=characters.charAt(randomIndex);
        }
    } while (wcagContrast.hex(prevColor, currentColor) < CONTRAST_THRESHOLD)

    return '#'+currentColor;
}

let checkFirstAndLastColor = (colorsArray) => {
    while (wcagContrast.hex(colorsArray[0], colorsArray[colorsArray.length -1]) < CONTRAST_THRESHOLD || wcagContrast.hex(colorsArray[0], colorsArray[1]) < CONTRAST_THRESHOLD) {
        colorsArray[0] = randomColor(colorsArray[1]);
    }
}

let round = (num) => Math.round(num*100)/100;

let chart = null;
$('#generate').click(()=>{
    let data = [];
    let labels = [];
    let pieces = $('#pieces').val();
    for(let i=0; i<pieces; i++) {
        labels.push(randomColor(labels[i - 1]));
        data.push(round(1/pieces));
    }
    if (labels.length > 2) {
        checkFirstAndLastColor(labels);
    }
    if(chart) chart.destroy();
    chart = new Chart($('#chart'), {
        type: 'pie',
        data: {
            labels: labels,
            datasets: [{
                data: data,
                backgroundColor: labels,
                borderWidth: 0
            }]
        },
        options: {
            plugins: {
                tooltip: {
                    callbacks: {
                        label: (context) => {
                            return context.parsed;
                        }
                    }
                }
            }
        }
    });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="
https://cdn.jsdelivr.net/npm/[email protected]/dist/index.umd.min.js
"></script>
<div>
  <input id="pieces" type="number" value="20" />
  <button id="generate">Generate Chart</button>
</div>
<canvas id="chart"></canvas>

主要思想是:

  1. 升级功能以检查是否与之前的颜色具有高对比度()。否则,生成另一种颜色,直到对比度为 (设置一个介于 1 和 21 之间的数字)。randomColor()currentColorprevColor>= CONTRAST_THRESHOLD

  2. 设置所有颜色时运行,以检查最后一种颜色和第一种颜色是否具有高对比度。如果没有,请更改第一种颜色,直到它与第二种颜色和最后一种颜色都达到高对比度。checkFirstAndLastColor

如前所述,代码可以在很多方面进行改进,我希望我的回答能给你正确的提示,以达到你的结果。

评论

0赞 Jordy 11/13/2023
谢谢,@Alberto。但是,此代码在输入奇数时会出现错误。
0赞 Alberto Fecchi 11/13/2023
对不起,我要修复它,我很快就测试了它
0赞 Alberto Fecchi 11/13/2023
刚刚检查了一下,这不是一个“真正的”代码问题,它更像是一个逻辑问题。如果设置为 2 似乎有效(并且颜色似乎具有足够的对比度)。使用一个值会无限循环,因为他找不到与数组的第二个和最后一个元素对比度高的颜色。这很正常:在 3 元素数组中将阈值 21(即可能的最大值)成像:不可能满足规则。这里的解决方案是尝试使用或完全删除逻辑。CONTRAST_THRESHOLD> 3checkFirstAndLastColor()CONTRAST_THRESHOLD=2checkFirstAndLastColor
0赞 Alberto Fecchi 11/13/2023
通过删除,第一种和最后一种颜色可能是低对比度。checkFirstAndLastColor
0赞 Jordy 11/15/2023
这是否意味着我无法更改大于 2 的阈值或不检查?checkFirstAndLastColor
1赞 A_mohii_ 11/15/2023 #2
const CONTRAST_THRESHOLD = 4.5;
let generatedColors = new Set();

let randomColor = () => {
    let currentColor;
    let contrastIsOk;
    // Generate a random color and ensure it has a high enough contrast ratio with all previously generated colors
    do {
        currentColor = '#' + Math.floor(Math.random() * 16777215).toString(16).padEnd(6, '0');
        contrastIsOk = [...generatedColors].every(col => wcagContrast.hex(col, currentColor) >= CONTRAST_THRESHOLD);
    } while (generatedColors.has(currentColor) || !contrastIsOk);
    
    generatedColors.add(currentColor);
    return currentColor;
}

let round = (num) => Math.round(num * 100) / 100;

let chart = null;
$('#generate').click(() => {
    let data = [];
    let colors = [];
    generatedColors.clear();
    let pieces = $('#pieces').val();

    // Generate a unique color for each piece of the pie chart
    for (let i = 0; i < pieces; i++) {
        colors.push(randomColor());
        data.push(round(1 / pieces));
    }

    // Special handling for odd numbers of pieces to avoid low contrast between the first and last segments
    if (pieces % 2 !== 0 && wcagContrast.hex(colors[0], colors[colors.length - 1]) < CONTRAST_THRESHOLD) {
        // Re-generate the last color to ensure contrast
        let lastColor;
        do {
            lastColor = randomColor();
        } while (wcagContrast.hex(colors[0], lastColor) < CONTRAST_THRESHOLD);
        colors[colors.length - 1] = lastColor;
    }

    if (chart) chart.destroy();

    // Create the pie chart with the generated colors
    chart = new Chart($('#chart'), {
        type: 'pie',
        data: {
            labels: colors.map((color, index) => `Color ${index + 1}`), // Label each color for clarity
            datasets: [{
                data: data,
                backgroundColor: colors,
                borderWidth: 0
            }]
        },
        options: {
            plugins: {
                tooltip: {
                    callbacks: {
                        label: (context) => `${context.label}: ${context.parsed}`
                    }
                }
            }
        }
    });
});

并确保包含 WCAG 对比库:

<script src="https://cdn.jsdelivr.net/npm/wca[email protected]/dist/index.umd.min.js"></script>

评论

0赞 Jordy 11/15/2023
i.stack.imgur.com/b7pqI.png - 顺便说一句,仍然获得相同的背景颜色
0赞 A_mohii_ 11/15/2023
我已经更新了源代码。你能再试一次吗?
0赞 Jordy 11/16/2023
仍然会遇到奇怪的问题:i.stack.imgur.com/Qb9VW.png
0赞 A_mohii_ 11/16/2023
我用新代码更新了我的回复。
1赞 Damian Chudobiński 11/18/2023 #3

有一个名为chroma js的插件可以提供帮助 很多颜色 https://gka.github.io/chroma.js/

编辑:

  • 现在它还检查第一个和最后一个是否有对比度
  • regeneratColorIteration 可以更改为任何正数以更改起始颜色
  • minContrast 现在更外部,用于配置相应图表部分的最小对比度
  • 检查其他图表部分是否尚未使用确切的颜色

代码利用答案

let minContrast = 2;

let randomByHsl = (colorNum) =>
{
    let hues = [0, 20, 30, 40, 50, 60, 80, 120, 160, 190, 210, 230, 260, 290, 330, 360];
    //hues = [...Array(37).keys()].map(x=>x*10);
    let lights = [20, 25, 30, 35, 40, 43, 45, 48, 50, 53, 55, 58, 60, 63, 65, 68, 70, 73, 75, 80];
    let goldenFrac = 0.5 * (3 - Math.sqrt(5));
    let x = (colorNum * goldenFrac % 1.0) * (hues.length - 1);
    //x=colorNum%(hues.length-1); // for point visualisation
    let i = Math.floor(x);
    let f = x % 1.0;
    let hue = (1.0 - f) * hues[i] + f * hues[i + 1];
    let light = (1.0 - f) * lights[i] + f * lights[i + 1];
    return [Math.round(hue * 100) / 100, 100, Math.round(light * 100) / 100];
}

let hslToHex = (h, s, l) =>
{
  l /= 100;
  s /= 100;

  const a = s * Math.min(l, 1 - l);
  const f = (n, h, l, a) => {
    const k = (n + h / 30) % 12;
    const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
    return Math.round(255 * color).toString(16).padStart(2, '0');
  };

  const red = f(0, h, l, a);
  const green = f(8, h, l, a);
  const blue = f(4, h, l, a);

  return `#${red}${green}${blue}`;
}

let makeColor = (colorNum) =>
{
  let newColor = randomByHsl(colorNum);
  return hslToHex(...newColor);
}

let calculateContrast = (color1, color2) =>
{
  return chroma.contrast(color1, color2);
}

let contrastCheck = (color, toCompare) =>
{
  if(toCompare.length)
  {
    let contrasts = [];

    toCompare.forEach((colorToCompare) =>
    {
      contrasts.push(calculateContrast(color, colorToCompare));
    });

    return contrasts.every(contrast => contrast > minContrast);
  }
  else
  {
    return true;
  }
}

let generateColors = (pieces) =>
{
  let labels = [];
  let data = [];
  let previousColor = null;
  let regeneratColorIteration = 1;

  for (let i = 0; i < pieces; i++)
  {
    regeneratColorIteration++;

    let newColor = makeColor(regeneratColorIteration);
    let colorsToCompare = [];

    if(previousColor)
    {
      colorsToCompare.push(previousColor);
    }

    if(i == pieces - 1
    && i != 0)
    {
      colorsToCompare.push(labels[0]);
    }

    while(labels.includes(newColor)
    && ! contrastCheck(newColor, colorsToCompare))
    {
        regeneratColorIteration++;
        newColor = makeColor(regeneratColorIteration);
    }

    labels.push(newColor);
    data.push(round(1 / pieces));
    previousColor = newColor;
  }

  return { labels, data };
}

let round = (num) => Math.round(num * 100) / 100;
let chart = null;

$('#generate').click(() =>
{
    let pieces = $('#pieces').val();

    if(chart)
    {
        chart.destroy();
    }

    const { labels, data } = generateColors(pieces);

    chart = new Chart($('#chart'),
    {
        type: 'pie',
        data:
        {
            labels: labels,
            datasets:
            [{
                data: data,
                backgroundColor: labels,
                borderWidth: 0
            }]
        },
        options:
        {
            plugins:
            {
                tooltip:
                {
                    callbacks:
                    {
                        label: (context) => 
                        {
                            return context.parsed;
                        }
                    }
                }
            }
        }
    });
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/chroma-js/2.1.1/chroma.min.js"></script>

        <div>
          <input id="pieces" type="number" value="20" />
          <button id="generate">Generate Chart</button>
        </div>
        <canvas id="chart"></canvas>

评论

0赞 Jordy 11/18/2023
i.stack.imgur.com/77HUC.png,仍然为第一件和最后一件作品获得相同的颜色
0赞 Damian Chudobiński 11/18/2023
我做了一些改进检查@Jordy