diff --git a/demo/index.html b/demo/index.html
index 4a61fc4..ddb0f0e 100644
--- a/demo/index.html
+++ b/demo/index.html
@@ -29,7 +29,7 @@
@mapbox/to-color
['deli', 6],
[1234, 6],
[5678, 6],
- ['mapbox.mapbox-streets-v8', 16],
+ ['mapbox.mapbox-streets-v8', 16, { saturation: 1.25, limit: ['blue'] }],
['mapbox.mapbox-streets-v8', 16, { saturation: 0.5 }],
['mapbox.mapbox-streets-v8', 16, { saturation: 1.25 }],
['mapbox.mapbox-streets-v8', 16, { brightness: 0.5 }],
diff --git a/package-lock.json b/package-lock.json
index 43f9ba8..0d25260 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "@mapbox/to-color",
- "version": "2.3.0",
+ "version": "2.3.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@mapbox/to-color",
- "version": "2.3.0",
+ "version": "2.3.1",
"license": "ISC",
"dependencies": {
"d3-color": "^3.1.0",
diff --git a/package.json b/package.json
index d8eaad7..30ef335 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@mapbox/to-color",
- "version": "2.3.0",
+ "version": "2.3.1",
"description": "Procedurally generate a deterministic, perceptually distributed color palette.",
"main": "dist/to-color.js",
"scripts": {
diff --git a/src/index.js b/src/index.js
index a569586..6c07bc5 100644
--- a/src/index.js
+++ b/src/index.js
@@ -5,14 +5,16 @@ import { Hsluv } from 'hsluv';
export default class toColor {
HUE_MAX = 360;
+ // HSLuv hue ranges. Each covers the full saturation/lightness spread of
+ // its color so neighbors overlap. Red wraps around 0, so it starts negative.
hues = {
- red: [-26, 18],
- orange: [18, 46],
- yellow: [46, 62],
- green: [62, 178],
- blue: [178, 257],
- purple: [257, 282],
- pink: [282, 334]
+ red: [-20, 34],
+ orange: [18, 71],
+ yellow: [59, 89],
+ green: [86, 190],
+ blue: [185, 277],
+ purple: [268, 295],
+ pink: [285, 360]
};
constructor(seed, options) {
@@ -109,8 +111,8 @@ export default class toColor {
const raw = hsl(c);
// Convert and round
- const hRounded = Math.round(this._clamp(raw.h, 0, 360));
- const sRounded = Math.round(this._clamp(raw.s * 100, 0, 100));
+ const hRounded = Math.round(this._clamp(raw.h || 0, 0, 360));
+ const sRounded = Math.round(this._clamp((raw.s || 0) * 100, 0, 100));
const lRounded = Math.round(this._clamp(raw.l * 100, 0, 100));
return {
@@ -128,17 +130,18 @@ export default class toColor {
hue = this._pseudoRandom([min, max]);
- // Red is on both ends of the color spectrum. Instead of storing red as two
- // ranges, lookup is grouped in `this.hue` as negative numbers.
- if (hue < 0) hue = this.HUE_MAX + hue;
-
// Limit the max of some hues if the option is passed.
const { limit } = this.options;
if (limit && limit.length) {
for (let i = 0; i !== limit.length; i++) {
const hueRange = this.hues?.[limit[i]];
- if (hueRange && hue > hueRange[0] && hue <= hueRange[1]) {
+ if (
+ hueRange &&
+ [hue, hue - this.HUE_MAX].some(
+ (h) => h > hueRange[0] && h <= hueRange[1]
+ )
+ ) {
return this._pickHue();
}
}
diff --git a/src/index.test.js b/src/index.test.js
index 4c86d97..6123a22 100644
--- a/src/index.test.js
+++ b/src/index.test.js
@@ -48,13 +48,13 @@ describe('toColor', () => {
});
describe('brightness/saturation', () => {
- const color = new toColor('penny', { brightness: 0.5, saturation: 0.5 });
+ const color = new toColor('penny', { brightness: 1.5, saturation: 0.5 });
it('returns deterministic color on getColor', () => {
expect(color.getColor()).toEqual({
hsl: {
- formatted: 'hsl(87, 14%, 31%)',
- raw: [87, 14, 31]
+ formatted: 'hsl(0, 0%, 100%)',
+ raw: [0, 0, 100]
}
});
});
@@ -62,8 +62,8 @@ describe('toColor', () => {
it('returns a different determinisic value calling getColor again', () => {
expect(color.getColor()).toEqual({
hsl: {
- formatted: 'hsl(337, 32%, 24%)',
- raw: [337, 32, 24]
+ formatted: 'hsl(338, 37%, 69%)',
+ raw: [338, 37, 69]
}
});
});
@@ -89,6 +89,16 @@ describe('toColor', () => {
}
});
});
+
+ it('never renders a limited hue', () => {
+ ['hi', 'mapbox.mapbox-streets-v8'].forEach((seed) => {
+ const color = new toColor(seed, { saturation: 1.25, limit: ['blue'] });
+ for (let i = 0; i !== 100; i++) {
+ const [h] = color.getColor().hsl.raw;
+ expect(h > 180 && h < 255).toBe(false);
+ }
+ });
+ });
});
describe('getColor seeding', () => {