【发布时间】:2022-01-22 00:10:48
【问题描述】:
我有一个折线图,它允许根据项目在同一个图表上显示多条线。但是,物品的数量是不同的,因为它是基于个人的。因此,我需要为所有项目获得不同的颜色。用户可以选择要在图表上显示的项目。因此,当用户选择更多可见的项目时,所需的颜色数量将增加。随着颜色数量的增加,每种颜色起初应该是不同的和可区分的,但逐渐变得彼此相似。想知道是否有实现上述目标的库或代码段?
【问题讨论】:
标签: javascript reactjs colors
我有一个折线图,它允许根据项目在同一个图表上显示多条线。但是,物品的数量是不同的,因为它是基于个人的。因此,我需要为所有项目获得不同的颜色。用户可以选择要在图表上显示的项目。因此,当用户选择更多可见的项目时,所需的颜色数量将增加。随着颜色数量的增加,每种颜色起初应该是不同的和可区分的,但逐渐变得彼此相似。想知道是否有实现上述目标的库或代码段?
【问题讨论】:
标签: javascript reactjs colors
编辑:用 ES6 箭头函数语法重写了函数。新增randomColours(quan) 功能。为 Method 1 和 Method 2 添加了 sn-ps 以更好地展示它们的工作原理。为方法2函数添加了更多参数。
试试这样的:
方法一:返回一个随机颜色,用一个rgb()颜色值表示。这种方法的缺点:因为颜色是随机的,你可以得到非常不同的颜色,但也可以得到非常相似的颜色。如果您想要一种更严格的方法来生成在色轮上等间距的颜色,请参阅方法 2。
function randomColour() {
return `rgb(${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)})`;
}
// ES6 Arrow Function Syntax (still does same thing)
const randomColour = () => `rgb(${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)})`;
使用:
let colOne = randomColour();
// colOne = "rbg(236, 174, 254)"
let colTwo = randomColour();
// colTwo = "rbg(47, 83, 164)"
let colThree = randomColour();
// colThree = "rbg(145, 214, 39)"
使用与方法一相同的原理修改函数,但不是返回单一随机颜色,而是返回一个随机颜色数组。
function randomColours(quan) {
let colours = [];
for (let i = 0; i < quan; i++) {
colours.push(`rgb(${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)})`);
}
return colours;
}
// ES6 Arrow Function Syntax (still does same thing)
const randomColours = quan => {
let colours = [];
for (let i = 0; i < quan; i++) {
colours.push(`rgb(${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)})`);
}
return colours;
}
使用:
const coloursArr = randomColours(5);
// coloursArr = [
// "rgb(183, 110, 102)",
// "rgb(59, 250, 185)",
// "rgb(188, 160, 67)",
// "rgb(241, 15, 18)",
// "rgb(136, 125, 129)"
// ]
方法 1 的片段:
const inp = document.querySelector("input");
const btn = document.querySelector("button");
const parentDiv = document.querySelector("div.container");
const validNumber = ({ value, min = undefined, max = undefined, integer = false, ignoreStrings = [] }) => {
if (typeof value != "string" && typeof value != "number") return console.error(`Value must be a number or string.`);
let valid = true;
let messages = [];
for (let i = 0; i < ignoreStrings.length; i++) {
value = String(value).split(ignoreStrings[i]).join("");
}
if (String(value) == "" || value == null || isNaN(value)) {
valid = false;
messages.push("Not a Number");
}
if (!messages.includes("Not a Number") && min && Number(value) < min) {
valid = false;
messages.push(`Minimum Value is ${min}`);
}
if (!messages.includes("Not a Number") && max && Number(value) > max) {
valid = false;
messages.push(`Maximum Value is ${max}`);
}
if (!messages.includes("Not a Number") && integer && !(!isNaN(value) && parseInt(Number(value)) == value && !isNaN(parseInt(value, 10)))) {
valid = false;
messages.push("Not an integer");
}
return { valid, messages };
}
// returns random colour!
const randomColour = () => `rgb(${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)})`;
// returns array of random colours!
const randomColours = quan => {
let colours = [];
for (let i = 0; i < quan; i++) {
colours.push(`rgb(${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)}, ${Math.round(Math.random() * 255)})`);
}
return colours;
}
const generateCircles = quan => {
// get an array of random colours
const colours = randomColours(quan);
parentDiv.innerHTML = "";
for (let i = 0; i < quan; i++) {
const elem = document.createElement("div");
elem.className = "circle";
// random colour for a circle
elem.style.backgroundColor = colours[i];
parentDiv.appendChild(elem);
}
}
btn.addEventListener("click", () => {
let details = validNumber({ value: inp.value, min: 1, max: 1000, integer: true, ignoreStrings: [" "] });
if (details.valid) {
generateCircles(Number(inp.value));
} else {
details.messagesStr = "";
details.messages.forEach((str, i) => details.messagesStr += ` ${i+1}. ${str}\n`);
alert(`Field_1 - Quantity:\n${details.messagesStr}`);
}
});
*, ::before, ::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
height: fit-content;
}
div.form {
padding: 2rem;
display: flex;
flex-direction: column;
align-items: start;
}
div.form > * { margin-bottom: 1rem; }
div.form input, div.form button {
padding: 0.6rem 0.8rem;
outline: none;
border-radius: 0.2rem;
font-family: sans-serif;
font-size: inherit;
font-weight: 500;
}
div.form input {
box-sizing: border-box;
height: fit-content;
border: 2px solid rgb(179, 179, 179);
transition: border-color 200ms ease-out;
}
div.form input:focus {
border-color: rgb(45, 160, 255);
}
div.form button {
border: 2px solid rgb(78, 175, 255);
background-color: rgb(78, 175, 255);
transition: background-color 200ms ease-out;
}
div.form button:hover {
border-color: rgb(45, 160, 255);
background-color: rgb(45, 160, 255);
}
div.form button:active {
transform: scale(0.98);
}
div.container {
padding: 0rem 1.5rem 2rem 1.5rem;
display: flex;
flex-wrap: wrap;
}
div.container div.circle {
width: 5rem;
height: 5rem;
border-radius: 100%;
margin: 0.5rem;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Colour Generation</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="form">
<input type="number" placeholder="no. of circles">
<button>Generate circles with random colours</button>
</div>
<div class="container">
</div>
<script src="script.js"></script>
</body>
</html>
方法二
function generateColours(quan, shuffle=false) {
let colours = [];
for (let i = 0; i < quan; i++) {
colours.push(`hsl(${(360/quan)*(quan-i)}, 80%, 50%)`);
}
if (shuffle) {
// uses the Fisher-Yates Shuffle to shuffle the colours
let currentIndex = colours.length, randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[colours[currentIndex], colours[randomIndex]] = [colours[randomIndex], colours[currentIndex]];
}
}
return colours;
}
// ES6 Arrow Function Syntax (does same thing)
const generateColours = (quan, shuffle=false) => {
let colours = [];
for (let i = 0; i < quan; i++) {
colours.push(`hsl(${(360/quan)*(quan-i)}, 80%, 50%)`);
}
if (shuffle) {
// uses the Fisher-Yates Shuffle to shuffle the colours
let currentIndex = colours.length, randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[colours[currentIndex], colours[randomIndex]] = [colours[randomIndex], colours[currentIndex]];
}
}
return colours;
}
使用:
let coloursArr = generateColours(5);
// coloursArr = [
// "hsl(360, 80%, 50%)",
// "hsl(288, 80%, 50%)",
// "hsl(216, 80%, 50%)",
// "hsl(144, 80%, 50%)",
// "hsl(72, 80%, 50%)"
// ]
颜色同样可区分。
我在上面的函数中增加了一些参数。它接受一个包含其参数的对象。还利用了 ES6 附带的对象解构,允许使用许多默认参数。这就是我想出的:
const generateColours = ({ quantity = 1, shuffle = false, order = "0,360", offset = 0, saturation = 80, lightness = 50 }) => {
let colours = [];
for (let i = 0; i < quantity; i++) {
let hue;
if (order == "0,360") hue = ((360/quantity) * (quantity+i)) - 360;
if (order == "360,0") hue = (360/quantity) * (quantity-i);
hue += offset;
colours.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`);
}
if (shuffle) {
// uses the Fisher-Yates Shuffle to shuffle the colours
let currentIndex = colours.length, randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[colours[currentIndex], colours[randomIndex]] = [colours[randomIndex], colours[currentIndex]];
}
}
return colours;
}
使用:
// all default parameters (one colour, shuffle set to false, order is '0,360', hue offset is 0, saturation is 80, lightness is 50)
generateColours({ });
// will return
// => [
// 'hsl(0, 80%, 50%)' // red
// ]
// ----------
// default parameters other than quantity is 6 (default is 1)
generateColours({ quantity: 6 });
// will return
// => [
// 'hsl(0, 80%, 50%)', // red
// 'hsl(60, 80%, 50%)', // yellow
// 'hsl(120, 80%, 50%)', // green
// 'hsl(180, 80%, 50%)', // cyan
// 'hsl(240, 80%, 50%)', // blue
// 'hsl(300, 80%, 50%)' // magenta
// ]
随机播放参数。默认情况下,它设置为 false。当设置为 true 时,它使用 Fisher-Yates Shuffle(一种无偏的随机播放算法)在返回颜色数组之前对其进行随机播放。
generateColours({ quantity: 6, shuffle: true });
// might return
// => [
// 'hsl(60, 80%, 50%)', // yellow
// 'hsl(180, 80%, 50%)', // cyan
// 'hsl(0, 80%, 50%)', // red
// 'hsl(240, 80%, 50%)', // blue
// 'hsl(300, 80%, 50%)', // magenta
// 'hsl(120, 80%, 50%)' // green
// ]
订单参数。更改数组中颜色的顺序(始终从红色开始 - 如果偏移量为 0)。订单可以是“0,360”或“360,0”。默认值为“0,360”。 注意:如果 shuffle 设置为 true,顺序将无效。
generateColours({ quantity: 6, order: "360,0" });
// will return
// => [
// 'hsl(360, 80%, 50%)', // red
// 'hsl(300, 80%, 50%)', // magenta
// 'hsl(240, 80%, 50%)', // blue
// 'hsl(180, 80%, 50%)', // cyan
// 'hsl(120, 80%, 50%)', // green
// 'hsl(60, 80%, 50%)' // yellow
// ]
偏移参数。按指定量偏移每种颜色的色调。默认为 0。偏移量可以是正数或负数。
generateColours({ quantity: 3, offset: 30 });
// will return
// => [
// 'hsl(30, 80%, 50%)' // orange
// 'hsl(150, 80%, 50%)' // cyan - green
// 'hsl(270, 80%, 50%)' // violet
// ]
饱和度和亮度是不言自明的。我在下面附加了一个 sn-p,以便您可以使用不同的参数。享受。希望它可以帮助任何人。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Colour Generation</title>
<style>
*, ::before, ::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
min-height: 100vh;
height: fit-content;
}
div.form {
padding: 2rem;
display: flex;
flex-direction: column;
align-items: start;
}
div.form div.inputs {
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
grid-gap: 1rem;
}
div.form div.inputs div.wrapper { width: fit-content; }
div.form div.inputs div.wrapper h2 { display: inline-block; }
div.form div.buttons { display: flex; }
div.form div.buttons > div { margin-right: 2rem; padding-left: 0.6rem; border-left: 3px solid rgb(78, 175, 255); }
div.form div.buttons > div > label { margin-left: 0.3rem; }
div.form > * { margin-bottom: 1rem; }
div.form input[type="number"], div.form button {
padding: 0.6rem 0.8rem;
outline: none;
border-radius: 0.2rem;
font-family: sans-serif;
font-size: inherit;
font-weight: 500;
}
div.form input[type="number"] {
box-sizing: border-box;
height: fit-content;
border: 2px solid rgb(179, 179, 179);
transition: border-color 200ms ease-out;
}
div.form input[type="number"]:focus {
border-color: rgb(45, 160, 255);
}
div.form button {
border: 2px solid rgb(78, 175, 255);
background-color: rgb(78, 175, 255);
transition: background-color 200ms ease-out, border-colour 200ms ease-out;
}
div.form button:hover {
border-color: rgb(45, 160, 255);
background-color: rgb(45, 160, 255);
}
div.form button:active {
transform: scale(0.98);
}
div.form h3 { margin-bottom: 0.25rem; font-family: sans-serif }
div.container {
padding: 0rem 1.5rem 2rem 1.5rem;
display: flex;
flex-wrap: wrap;
}
div.container div.circle {
width: 5rem;
height: 5rem;
border-radius: 100%;
margin: 0.5rem;
}
</style>
</head>
<body>
<div class="form">
<div class="inputs">
<div class="wrapper">
<h3>Amount</h3>
<input type="number" placeholder="no. of circles (def. = 1)" id="amount" value="1">
</div>
<div class="wrapper">
<h3>Lightness</h3>
<input type="number" placeholder="lightness (def. = 50)" id="lightness" value="50">%
</div>
<div class="wrapper">
<h3>Hue Offset</h3>
<input type="number" placeholder="hue offset (def. = 0)" id="offset" value="0">
</div>
<div class="wrapper">
<h3>Saturation</h3>
<input type="number" placeholder="saturation (def. = 80)" id="saturation" value="80">%
</div>
</div>
<div class="buttons">
<div class="checkboxes">
<h3>Shuffle</h3>
<input type="radio" id="shuffle-first" name="shuffle" checked>
<label for="shuffle-first">False</label><br>
<input type="radio" id="shuffle-second" name="shuffle">
<label for="shuffle-second">True</label>
</div>
<div class="radio-buttons">
<h3>Order</h3>
<input type="radio" id="order-first" name="order" checked>
<label for="order-first">0,360</label><br>
<input type="radio" id="order-second" name="order">
<label for="order-second">360,0</label>
</div>
</div>
<button>Generate circles with equally spaced colours</button>
</div>
<div class="container">
</div>
<script>
const inpAmount = document.querySelector("input#amount");
const inpOffset = document.querySelector("input#offset");
const inpLightness = document.querySelector("input#lightness");
const inpSaturation = document.querySelector("input#saturation");
const radioShuffleFirst = document.querySelector("input#shuffle-first");
const radioOrderFirst = document.querySelector("input#order-first");
const btn = document.querySelector("button");
const parentDiv = document.querySelector("div.container");
const validNumber = ({ value, min = undefined, max = undefined, integer = false, ignoreStrings = [] }) => {
if (typeof value != "string" && typeof value != "number") return console.error(`Value must be a number or string.`);
let valid = true;
let messages = [];
for (let i = 0; i < ignoreStrings.length; i++) {
value = String(value).split(ignoreStrings[i]).join("");
}
if (String(value) == "" || value == null || isNaN(value)) {
valid = false;
messages.push("Not a Number");
}
if (!messages.includes("Not a Number") && min && Number(value) < min) {
valid = false;
messages.push(`Minimum Value is ${min}`);
}
if (!messages.includes("Not a Number") && max && Number(value) > max) {
valid = false;
messages.push(`Maximum Value is ${max}`);
}
if (!messages.includes("Not a Number") && integer && !(!isNaN(value) && parseInt(Number(value)) == value && !isNaN(parseInt(value, 10)))) {
valid = false;
messages.push("Not an integer");
}
return { valid, messages };
}
// returns an array of equally spaced colours!
const generateColours = ({ quantity = 1, shuffle = false, order = "0,360", offset = 0, saturation = 80, lightness = 50 }) => {
let colours = [];
for (let i = 0; i < quantity; i++) {
let hue;
if (order == "0,360") hue = ((360/quantity) * (quantity+i)) - 360;
if (order == "360,0") hue = (360/quantity) * (quantity-i);
hue += offset;
colours.push(`hsl(${hue}, ${saturation}%, ${lightness}%)`);
}
if (shuffle) {
// uses the Fisher-Yates Shuffle to shuffle the colours
let currentIndex = colours.length, randomIndex;
while (currentIndex != 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[colours[currentIndex], colours[randomIndex]] = [colours[randomIndex], colours[currentIndex]];
}
}
return colours;
}
const generateCircles = (args) => {
// get an array of equally spaced colours
const colours = generateColours(args);
parentDiv.innerHTML = "";
for (let i = 0; i < args.quantity; i++) {
const elem = document.createElement("div");
elem.className = "circle";
// random colour for a circle
elem.style.backgroundColor = colours[i];
parentDiv.appendChild(elem);
}
}
btn.addEventListener("click", () => {
let quanInput = validNumber({ value: inpAmount.value, min: 1, max: 1000, integer: true, ignoreStrings: [" "] });
let offsetInput = validNumber({ value: inpOffset.value, min: -360, max: 360, ignoreStrings: [" "] });
let lightInput = validNumber({ value: inpLightness.value, min: 0, max: 100, ignoreStrings: ["%", " "] });
let satInput = validNumber({ value: inpSaturation.value, min: 0, max: 100, ignoreStrings: ["%", " "] });
if (quanInput.valid && offsetInput.valid && lightInput.valid && satInput.valid) {
let arguments = {
quantity: Number(inpAmount.value),
shuffle: (radioShuffleFirst.checked) ? false : true,
order: (radioOrderFirst.checked) ? "0,360" : "360,0",
offset: Number(inpOffset.value),
saturation: Number(inpSaturation.value),
lightness: Number(inpLightness.value)
}
generateCircles(arguments);
}
else {
quanInput.messagesStr = "";
quanInput.messages.forEach((str, i) => quanInput.messagesStr += ` ${i+1}. ${str}\n`);
offsetInput.messagesStr = "";
offsetInput.messages.forEach((str, i) => offsetInput.messagesStr += ` ${i+1}. ${str}\n`);
satInput.messagesStr = "";
satInput.messages.forEach((str, i) => satInput.messagesStr += ` ${i+1}. ${str}\n`);
lightInput.messagesStr = "";
lightInput.messages.forEach((str, i) => lightInput.messagesStr += ` ${i+1}. ${str}\n`);
alert(`Field_1 - Quantity:\n${quanInput.messagesStr}\nField_2 - Offset:\n${offsetInput.messagesStr}\nField_3 - Lightness:\n${lightInput.messagesStr}\nField_4 - Saturation:\n${satInput.messagesStr}`);
}
});
</script>
</body>
</html>
【讨论】:
我想到的一种方法:
如果您以相等的间隔绕过 HSV 色轮,那么您会得到不同的颜色。 HSV 在 [0,1] 的范围内。除非您需要几十种颜色,否则将 Saturation 和 Value 设置为 1。
创建您想要的颜色数量数组的方法:
const colorCount = 24;
const dh = 1 / colorCount;
let colors = [];
for(let i=0;i<colorCount;i++) {
let rgb = HSVtoRGB(dh*i,1,1);
colors.push(rgb);
}
// And our helper function:
/* accepts parameters
* h Object = {h:x, s:y, v:z}
* OR
* h, s, v
*/
function HSVtoRGB(h, s, v) {
var r, g, b, i, f, p, q, t;
if (arguments.length === 1) {
s = h.s, v = h.v, h = h.h;
}
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
}
【讨论】: