Skip to content

Commit

Permalink
Make it easier to access colorBrewer palette keys (#351)
Browse files Browse the repository at this point in the history
* feat: make it easier to access colorbrewer palette names

* docs: explain differences to official colorbrewer scales

* chore: add test

* fix typo

* only use Proxy if it's available

* fix: remove circular dependency

* build & docs
  • Loading branch information
gka authored Aug 17, 2024
1 parent 89c416a commit ea7f534
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 34 deletions.
5 changes: 4 additions & 1 deletion dist/chroma-light.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,13 @@
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.chroma = factory());
})(this, (function () { 'use strict';

var min$1 = Math.min;
var max$1 = Math.max;

function limit (x, low, high) {
if ( high === void 0 ) high = 1;

return min(max(low, x), high);
return min$1(max$1(low, x), high);
}

function clip_rgb (rgb) {
Expand Down
2 changes: 1 addition & 1 deletion dist/chroma-light.min.cjs

Large diffs are not rendered by default.

32 changes: 22 additions & 10 deletions dist/chroma.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,14 @@
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.chroma = factory());
})(this, (function () { 'use strict';

var min$4 = Math.min;
var max$4 = Math.max;

function limit (x, low, high) {
if ( low === void 0 ) low = 0;
if ( high === void 0 ) high = 1;

return min$3(max$3(low, x), high);
return min$4(max$4(low, x), high);
}

function clip_rgb (rgb) {
Expand All @@ -84,7 +87,7 @@

// ported from jQuery's $.type
var classToType = {};
for (var i$1 = 0, list$1 = [
for (var i = 0, list = [
'Boolean',
'Number',
'String',
Expand All @@ -94,8 +97,8 @@
'RegExp',
'Undefined',
'Null'
]; i$1 < list$1.length; i$1 += 1) {
var name = list$1[i$1];
]; i < list.length; i += 1) {
var name = list[i];

classToType[("[object " + name + "]")] = name.toLowerCase();
}
Expand Down Expand Up @@ -3858,12 +3861,21 @@
Pastel1: ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#f2f2f2']
};

// add lowercase aliases for case-insensitive matches
for (var i = 0, list = Object.keys(colorbrewer); i < list.length; i += 1) {
var key = list[i];
var colorbrewerTypes = Object.keys(colorbrewer);
var typeMap = new Map(colorbrewerTypes.map(function (key) { return [key.toLowerCase(), key]; }));

colorbrewer[key.toLowerCase()] = colorbrewer[key];
}
// use Proxy to allow case-insensitive access to palettes
var colorbrewerProxy = typeof Proxy === 'function' ? new Proxy(colorbrewer, {
get: function get(target, prop) {
var lower = prop.toLowerCase();
if (typeMap.has(lower)) {
return target[typeMap.get(lower)];
}
},
getOwnPropertyNames: function getOwnPropertyNames() {
return Object.getOwnPropertyNames(colorbrewerTypes);
}
}) : colorbrewer;

// feel free to comment out anything to rollup
// a smaller chroma.js bundle
Expand All @@ -3873,7 +3885,7 @@
average: average,
bezier: bezier$1,
blend: blend,
brewer: colorbrewer,
brewer: colorbrewerProxy,
Color: Color,
colors: w3cx11,
contrast: contrast,
Expand Down
2 changes: 1 addition & 1 deletion dist/chroma.min.cjs

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -277,9 +277,26 @@ <h4 id="-color1-color2-kl-1-kc-1-kh-1-">(color1, color2, Kl=1, Kc=1, Kh=1)</h4>

</code></pre>
<h3 id="chroma-brewer">chroma.brewer</h3>
<p>chroma.brewer is an map of <a href="http://colorbrewer2.org/">ColorBrewer scales</a> that are included in chroma.js for convenience. chroma.scale uses the colors to construct.</p>
<p>chroma.brewer is an map of <a href="http://colorbrewer2.org/">ColorBrewer palettes</a> that are included in chroma.js for convenience. chroma.scale uses the colors to construct. </p>
<pre><code class="lang-js">chroma.brewer.OrRd
</code></pre>
<p>Note that chroma.js only includes the 9-step versions of the palettes (11 steps for the diverging palettes). So, for instance, if you use chroma.js to construct a 5-color palette, they will be different from the &quot;official&quot; 5-color palettes in ColorBrewer (which have lower contrast).</p>
<pre><code class="lang-js">chroma.scale(&#39;RdBu&#39;).colors(5);
// offical 5-color RdBu:
[&#39;#ca0020&#39;, &#39;#f4a582&#39;, &#39;#f7f7f7&#39;, &#39;#92c5de&#39;, &#39;#0571b0&#39;]
</code></pre>
<p>One way to compensate for this would be to &quot;slice off&quot; the extreme colors:</p>
<pre><code class="lang-js">chroma
.scale(chroma.brewer.RdBu.slice(1,-1))
.colors(5);
</code></pre>
<p>Of course you can also just construct the scale from the official 5-step colors that you can copy and paste from <a href="https://colorbrewer2.org/#type=diverging&amp;scheme=RdBu&amp;n=5">colorbrewer2.org</a>:</p>
<pre><code class="lang-js">chroma.scale([&#39;#ca0020&#39;, &#39;#f4a582&#39;, &#39;#f7f7f7&#39;, &#39;#92c5de&#39;, &#39;#0571b0&#39;])
</code></pre>
<p>You can access a list of all available palettes via <code>Object.keys(chroma.brewer)</code>:</p>
<pre><code class="lang-js">Object.keys(chroma.brewer)
// [&#39;OrRd&#39;, &#39;PuBu&#39;, &#39;BuPu&#39;, &#39;Oranges&#39;, &#39;BuGn&#39;, &#39;YlOrBr&#39;, &#39;YlGn&#39;, &#39;Reds&#39;, &#39;RdPu&#39;, &#39;Greens&#39;, &#39;YlGnBu&#39;, &#39;Purples&#39;, &#39;GnBu&#39;, &#39;Greys&#39;, &#39;YlOrRd&#39;, &#39;PuRd&#39;, &#39;Blues&#39;, &#39;PuBuGn&#39;, &#39;Viridis&#39;, &#39;Spectral&#39;, &#39;RdYlGn&#39;, &#39;RdBu&#39;, &#39;PiYG&#39;, &#39;PRGn&#39;, &#39;RdYlBu&#39;, &#39;BrBG&#39;, &#39;RdGy&#39;, &#39;PuOr&#39;, &#39;Set2&#39;, &#39;Accent&#39;, &#39;Set1&#39;, &#39;Set3&#39;, &#39;Dark2&#39;, &#39;Paired&#39;, &#39;Pastel2&#39;, &#39;Pastel1&#39;]
</code></pre>
<h3 id="chroma-limits">chroma.limits</h3>
<h4 id="-data-mode-n-">(data, mode, n)</h4>
<p>A helper function that computes class breaks for you, based on data. It supports the modes <em>equidistant</em> (e), <em>quantile</em> (q), <em>logarithmic</em> (l), and <em>k-means</em> (k). Let&#39;s take a few numbers as sample data.</p>
Expand Down
5 changes: 4 additions & 1 deletion docs/libs/chroma-light.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,13 @@
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.chroma = factory());
})(this, (function () { 'use strict';

var min$1 = Math.min;
var max$1 = Math.max;

function limit (x, low, high) {
if ( high === void 0 ) high = 1;

return min(max(low, x), high);
return min$1(max$1(low, x), high);
}

function clip_rgb (rgb) {
Expand Down
2 changes: 1 addition & 1 deletion docs/libs/chroma-light.min.cjs

Large diffs are not rendered by default.

32 changes: 22 additions & 10 deletions docs/libs/chroma.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,14 @@
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.chroma = factory());
})(this, (function () { 'use strict';

var min$4 = Math.min;
var max$4 = Math.max;

function limit (x, low, high) {
if ( low === void 0 ) low = 0;
if ( high === void 0 ) high = 1;

return min$3(max$3(low, x), high);
return min$4(max$4(low, x), high);
}

function clip_rgb (rgb) {
Expand All @@ -84,7 +87,7 @@

// ported from jQuery's $.type
var classToType = {};
for (var i$1 = 0, list$1 = [
for (var i = 0, list = [
'Boolean',
'Number',
'String',
Expand All @@ -94,8 +97,8 @@
'RegExp',
'Undefined',
'Null'
]; i$1 < list$1.length; i$1 += 1) {
var name = list$1[i$1];
]; i < list.length; i += 1) {
var name = list[i];

classToType[("[object " + name + "]")] = name.toLowerCase();
}
Expand Down Expand Up @@ -3858,12 +3861,21 @@
Pastel1: ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#f2f2f2']
};

// add lowercase aliases for case-insensitive matches
for (var i = 0, list = Object.keys(colorbrewer); i < list.length; i += 1) {
var key = list[i];
var colorbrewerTypes = Object.keys(colorbrewer);
var typeMap = new Map(colorbrewerTypes.map(function (key) { return [key.toLowerCase(), key]; }));

colorbrewer[key.toLowerCase()] = colorbrewer[key];
}
// use Proxy to allow case-insensitive access to palettes
var colorbrewerProxy = typeof Proxy === 'function' ? new Proxy(colorbrewer, {
get: function get(target, prop) {
var lower = prop.toLowerCase();
if (typeMap.has(lower)) {
return target[typeMap.get(lower)];
}
},
getOwnPropertyNames: function getOwnPropertyNames() {
return Object.getOwnPropertyNames(colorbrewerTypes);
}
}) : colorbrewer;

// feel free to comment out anything to rollup
// a smaller chroma.js bundle
Expand All @@ -3873,7 +3885,7 @@
average: average,
bezier: bezier$1,
blend: blend,
brewer: colorbrewer,
brewer: colorbrewerProxy,
Color: Color,
colors: w3cx11,
contrast: contrast,
Expand Down
2 changes: 1 addition & 1 deletion docs/libs/chroma.min.cjs

Large diffs are not rendered by default.

31 changes: 30 additions & 1 deletion docs/src/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -378,12 +378,41 @@ chroma.deltaE('#000000', '#ffffff');

### chroma.brewer

chroma.brewer is an map of [ColorBrewer scales](http://colorbrewer2.org/) that are included in chroma.js for convenience. chroma.scale uses the colors to construct.
chroma.brewer is an map of [ColorBrewer palettes](http://colorbrewer2.org/) that are included in chroma.js for convenience. chroma.scale uses the colors to construct.

```js
chroma.brewer.OrRd
```

Note that chroma.js only includes the 9-step versions of the palettes (11 steps for the diverging palettes). So, for instance, if you use chroma.js to construct a 5-color palette, they will be different from the "official" 5-color palettes in ColorBrewer (which have lower contrast).

```js
chroma.scale('RdBu').colors(5);
// offical 5-color RdBu:
['#ca0020', '#f4a582', '#f7f7f7', '#92c5de', '#0571b0']
```

One way to compensate for this would be to "slice off" the extreme colors:

```js
chroma
.scale(chroma.brewer.RdBu.slice(1,-1))
.colors(5);
```

Of course you can also just construct the scale from the official 5-step colors that you can copy and paste from [colorbrewer2.org](https://colorbrewer2.org/#type=diverging&scheme=RdBu&n=5):

```js
chroma.scale(['#ca0020', '#f4a582', '#f7f7f7', '#92c5de', '#0571b0'])
```

You can access a list of all available palettes via `Object.keys(chroma.brewer)`:

```js
Object.keys(chroma.brewer)
// ['OrRd', 'PuBu', 'BuPu', 'Oranges', 'BuGn', 'YlOrBr', 'YlGn', 'Reds', 'RdPu', 'Greens', 'YlGnBu', 'Purples', 'GnBu', 'Greys', 'YlOrRd', 'PuRd', 'Blues', 'PuBuGn', 'Viridis', 'Spectral', 'RdYlGn', 'RdBu', 'PiYG', 'PRGn', 'RdYlBu', 'BrBG', 'RdGy', 'PuOr', 'Set2', 'Accent', 'Set1', 'Set3', 'Dark2', 'Paired', 'Pastel2', 'Pastel1']
```

### chroma.limits
#### (data, mode, n)

Expand Down
24 changes: 19 additions & 5 deletions src/colors/colorbrewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,23 @@ const colorbrewer = {
Pastel1: ['#fbb4ae', '#b3cde3', '#ccebc5', '#decbe4', '#fed9a6', '#ffffcc', '#e5d8bd', '#fddaec', '#f2f2f2']
};

// add lowercase aliases for case-insensitive matches
for (let key of Object.keys(colorbrewer)) {
colorbrewer[key.toLowerCase()] = colorbrewer[key];
}
const colorbrewerTypes = Object.keys(colorbrewer);
const typeMap = new Map(colorbrewerTypes.map((key) => [key.toLowerCase(), key]));

export default colorbrewer;
// use Proxy to allow case-insensitive access to palettes
const colorbrewerProxy =
typeof Proxy === 'function'
? new Proxy(colorbrewer, {
get(target, prop) {
const lower = prop.toLowerCase();
if (typeMap.has(lower)) {
return target[typeMap.get(lower)];
}
},
getOwnPropertyNames() {
return Object.getOwnPropertyNames(colorbrewerTypes);
}
})
: colorbrewer;

export default colorbrewerProxy;
2 changes: 1 addition & 1 deletion src/utils/limit.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { min, max } from './index.js';
const { min, max } = Math;

export default (x, low = 0, high = 1) => {
return min(max(low, x), high);
Expand Down
24 changes: 24 additions & 0 deletions test/colorbrewer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, it, expect } from 'vitest';
import chroma from 'chroma-js';

describe('ColorBrewer palettes', () => {
it('should have all palettes', () => {
expect(chroma.brewer).toBeDefined();
expect(Object.keys(chroma.brewer).length).toBe(36);
});

it('brewer keys are camel-cased', () => {
expect(Object.keys(chroma.brewer)[0]).toBe('OrRd');
expect(Object.keys(chroma.brewer)[1]).toBe('PuBu');
});

it('supports case-insensitive access to palettes', () => {
expect(chroma.brewer.RdYlBu).toBeDefined();
expect(chroma.brewer.rdylbu).toBeDefined();
expect(chroma.brewer.RdylBU).toBeDefined();
});

it('non existing palettes are still undefined', () => {
expect(chroma.brewer.notHere).toBeUndefined();
});
});
4 changes: 4 additions & 0 deletions test/scales.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ describe('Some tests for scale()', () => {
it('ends black', () => {
expect(f(100).hex()).toBe('#006837');
});

it('returns domain', () => {
expect(f.domain()).toEqual([0, 100]);
});
});

describe('colorbrewer scale - lowercase', () => {
Expand Down

0 comments on commit ea7f534

Please sign in to comment.