提问人:Jordy 提问时间:11/10/2023 更新时间:11/18/2023 访问量:199
具有最佳随机背景颜色的图表.js饼图
Chart.js Pie with best random background color
问:
我有一些数据将以饼图形式可视化。
但是,我希望 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>
你能帮我生成一些随机的背景颜色,但相邻颜色有很好的对比度分数或不会伤害眼睛吗?
答:
一个可能的解决方案是使用颜色对比度检查器库,如下所示: https://www.npmjs.com/package/wcag-contrast
它基于 WCAG 对比度标准:https://www.w3.org/TR/WCAG20/#contrast-ratiodef
使用该函数,您可以检查两个十六进制值之间的对比度,即介于(低对比度)和(高对比度)之间的值。hex()
1
21
您的代码可以通过许多不同的方式进行改进,无论如何,我只添加了“核心”改进以添加所讨论的功能(以便您可以更好地理解我所做的工作)
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 和 21 之间的数字)。
randomColor()
currentColor
prevColor
>= CONTRAST_THRESHOLD
设置所有颜色时运行,以检查最后一种颜色和第一种颜色是否具有高对比度。如果没有,请更改第一种颜色,直到它与第二种颜色和最后一种颜色都达到高对比度。
checkFirstAndLastColor
如前所述,代码可以在很多方面进行改进,我希望我的回答能给你正确的提示,以达到你的结果。
评论
CONTRAST_THRESHOLD
> 3
checkFirstAndLastColor()
CONTRAST_THRESHOLD=2
checkFirstAndLastColor
checkFirstAndLastColor
checkFirstAndLastColor
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>
评论
有一个名为chroma js的插件可以提供帮助 很多颜色 https://gka.github.io/chroma.js/
编辑:
- 现在它还检查第一个和最后一个是否有对比度
- regeneratColorIteration 可以更改为任何正数以更改起始颜色
- minContrast 现在更外部,用于配置相应图表部分的最小对比度
- 检查其他图表部分是否尚未使用确切的颜色
代码利用答案
- @user1537366 来自生成可区分给人类的随机颜色
- @icl7126从RGB到十六进制,十六进制到RGB
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>
评论