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', () => {