d3-scale-cluster
Advanced tools
Comparing version 1.1.7 to 1.2.0
# Changelog | ||
### 1.2.0 | ||
* Add helpers for computation in web worker (thanks @furstenheim-geoblink) | ||
### 1.1.7 | ||
@@ -4,0 +8,0 @@ |
@@ -1,1 +0,1 @@ | ||
!function(r,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("d3scaleCluster",[],t):"object"==typeof exports?exports.d3scaleCluster=t():r.d3scaleCluster=t()}(this,function(){return function(r){function t(e){if(n[e])return n[e].exports;var o=n[e]={exports:{},id:e,loaded:!1};return r[e].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=r,t.c=n,t.p="",t(0)}([function(r,t,n){function e(){function r(){if(!(a.length<=2)){t=o(n,Math.min(n.length,a.length)),u=[];for(var r=0;r<t.length;r++)u.push(t[r][0])}}var t=[],n=[],a=[],u=[],f=function(r){if(0!==t.length){for(var n=u.length-1;n>=0;n--)if(r>=u[n])return a[n];return a[0]}};return f.domain=function(){return arguments.length?(n=arguments[0],r(),f):n},f.range=function(){if(arguments.length){var t=arguments[0],n=t.length!==a.length;return a=t,n&&r(),f}return a},f.invertExtent=function(r){for(var t=NaN,n=NaN,e=0;e<a.length;e++)if(a[e]===r){t=u[e],n=e+1<a.length?u[e+1]:NaN;break}return[t,n]},f.clusters=function(){return u.slice(1)},f.copy=function(){return e().domain(n).range(a)},f}var o=n(1);"object"==typeof d3&&(d3.scaleCluster=e),r.exports=e},function(r,t){function n(r){return r.slice().sort(function(r,t){return r-t})}function e(r){for(var t,n=0,e=0;e<r.length;e++)0!==e&&r[e]===t||(t=r[e],n++);return n}function o(r,t){for(var n=[],e=0;e<r;e++){for(var o=[],a=0;a<t;a++)o.push(0);n.push(o)}return n}function a(r,t,n,e){var o;if(r>0){var a=(n[t]-n[r-1])/(t-r+1);o=e[t]-e[r-1]-(t-r+1)*a*a}else o=e[t]-n[t]*n[t]/(t+1);return o<0?0:o}function u(r,t,n,e,o,f,i){if(!(r>t)){var l=Math.floor((r+t)/2);e[n][l]=e[n-1][l-1],o[n][l]=l;var c=n;r>n&&(c=Math.max(c,o[n][r-1]||0)),c=Math.max(c,o[n-1][l]||0);var h=l-1;t<e.length-1&&(h=Math.min(h,o[n][t+1]||0));for(var s,v,g,p,d=h;d>=c&&(s=a(d,l,f,i),!(s+e[n-1][c-1]>=e[n][l]));--d)v=a(c,l,f,i),g=v+e[n-1][c-1],g<e[n][l]&&(e[n][l]=g,o[n][l]=c),c++,p=s+e[n-1][d-1],p<e[n][l]&&(e[n][l]=p,o[n][l]=d);u(r,l-1,n,e,o,f,i),u(l+1,t,n,e,o,f,i)}}function f(r,t,n){for(var e=t[0].length,o=new Array(e),f=new Array(e),i=r[Math.floor(e/2)],l=0;l<e;++l)0===l?(o[0]=r[0]-i,f[0]=(r[0]-i)*(r[0]-i)):(o[l]=o[l-1]+r[l]-i,f[l]=f[l-1]+(r[l]-i)*(r[l]-i)),t[0][l]=a(0,l,o,f),n[0][l]=0;for(var c,h=1;h<t.length;++h)c=h<t.length-1?h:e-1,u(c,e-1,h,t,n,o,f)}function i(r,t){if(t>r.length)throw new Error("Cannot generate more classes than there are data values");var a=r.length,u=n(r),i=e(u);if(1===i)return[u];t=Math.min(i,t);var l=o(t,a),c=o(t,a);f(u,l,c);for(var h=[],s=c[0].length-1,v=c.length-1;v>=0;v--){var g=c[v][s];h[v]=u.slice(g,s+1),v>0&&(s=g-1)}return h}r.exports=i}])}); | ||
!function(r,n){"object"==typeof exports&&"object"==typeof module?module.exports=n():"function"==typeof define&&define.amd?define("d3scaleCluster",[],n):"object"==typeof exports?exports.d3scaleCluster=n():r.d3scaleCluster=n()}(this,function(){return function(r){function n(e){if(t[e])return t[e].exports;var o=t[e]={exports:{},id:e,loaded:!1};return r[e].call(o.exports,o,o.exports,n),o.loaded=!0,o.exports}var t={};return n.m=r,n.c=t,n.p="",n(0)}([function(r,n,t){function e(){function r(){if(!(a.length<=2)){var r=o(t,Math.min(t.length,a.length));n=0!==r.length,u=[];for(var e=0;e<r.length;e++)u.push(r[e][0])}}var n=!1,t=[],a=[],u=[],i=function(r){if(n){for(var t=u.length-1;t>=0;t--)if(r>=u[t])return a[t];return a[0]}};return i.domain=function(){return arguments.length?(t=arguments[0],r(),i):t},i.range=function(){if(arguments.length){var n=arguments[0],t=n.length!==a.length;return a=n,t&&r(),i}return a},i.invertExtent=function(r){for(var n=NaN,t=NaN,e=0;e<a.length;e++)if(a[e]===r){n=u[e],t=e+1<a.length?u[e+1]:NaN;break}return[n,t]},i.clusters=function(){return u.slice(1)},i.export=function(){return{isReady:n,domain:t,range:a,breakpoints:u}},i.import=function(r){if(!r)throw new Error("Import requires parameters");return n=r.isReady,t=r.domain,a=r.range,u=r.breakpoints,i},i.copy=function(){return e().domain(t).range(a)},i}var o=t(1);"object"==typeof d3&&(d3.scaleCluster=e),r.exports=e},function(r,n){function t(r){return r.slice().sort(function(r,n){return r-n})}function e(r){for(var n,t=0,e=0;e<r.length;e++)0!==e&&r[e]===n||(n=r[e],t++);return t}function o(r,n){for(var t=[],e=0;e<r;e++){for(var o=[],a=0;a<n;a++)o.push(0);t.push(o)}return t}function a(r,n,t,e){var o;if(r>0){var a=(t[n]-t[r-1])/(n-r+1);o=e[n]-e[r-1]-(n-r+1)*a*a}else o=e[n]-t[n]*t[n]/(n+1);return o<0?0:o}function u(r,n,t,e,o,i,f){if(!(r>n)){var l=Math.floor((r+n)/2);e[t][l]=e[t-1][l-1],o[t][l]=l;var c=t;r>t&&(c=Math.max(c,o[t][r-1]||0)),c=Math.max(c,o[t-1][l]||0);var s=l-1;n<e.length-1&&(s=Math.min(s,o[t][n+1]||0));for(var h,p,g,v,d=s;d>=c&&(h=a(d,l,i,f),!(h+e[t-1][c-1]>=e[t][l]));--d)p=a(c,l,i,f),g=p+e[t-1][c-1],g<e[t][l]&&(e[t][l]=g,o[t][l]=c),c++,v=h+e[t-1][d-1],v<e[t][l]&&(e[t][l]=v,o[t][l]=d);u(r,l-1,t,e,o,i,f),u(l+1,n,t,e,o,i,f)}}function i(r,n,t){for(var e=n[0].length,o=new Array(e),i=new Array(e),f=r[Math.floor(e/2)],l=0;l<e;++l)0===l?(o[0]=r[0]-f,i[0]=(r[0]-f)*(r[0]-f)):(o[l]=o[l-1]+r[l]-f,i[l]=i[l-1]+(r[l]-f)*(r[l]-f)),n[0][l]=a(0,l,o,i),t[0][l]=0;for(var c,s=1;s<n.length;++s)c=s<n.length-1?s:e-1,u(c,e-1,s,n,t,o,i)}function f(r,n){if(n>r.length)throw new Error("Cannot generate more classes than there are data values");var a=r.length,u=t(r),f=e(u);if(1===f)return[u];n=Math.min(f,n);var l=o(n,a),c=o(n,a);i(u,l,c);for(var s=[],h=c[0].length-1,p=c.length-1;p>=0;p--){var g=c[p][h];s[p]=u.slice(g,h+1),p>0&&(h=g-1)}return s}r.exports=f}])}); |
{ | ||
"name": "d3-scale-cluster", | ||
"version": "1.1.7", | ||
"version": "1.2.0", | ||
"description": "D3 scale that clusters data into discrete groups", | ||
@@ -8,4 +8,5 @@ "repository": "schnerd/d3-scale-cluster", | ||
"scripts": { | ||
"test": "node spec/index.js", | ||
"build": "webpack" | ||
"test": "npm run lint && node spec/index.js", | ||
"build": "webpack", | ||
"lint": "eslint src/* spec/*" | ||
}, | ||
@@ -15,7 +16,8 @@ "author": "David Schnurr", | ||
"devDependencies": { | ||
"d3": "^4.2.2", | ||
"eslint": "^3.5.0", | ||
"eslint-config-standard": "^6.0.0", | ||
"eslint-plugin-promise": "^2.0.1", | ||
"eslint-plugin-standard": "^2.0.0", | ||
"eslint": "^4.15.0", | ||
"eslint-config-standard": "^11.0.0-beta.0", | ||
"eslint-plugin-import": "^2.8.0", | ||
"eslint-plugin-node": "^5.2.1", | ||
"eslint-plugin-promise": "^3.6.0", | ||
"eslint-plugin-standard": "^3.0.1", | ||
"jasmine": "^2.5.1", | ||
@@ -22,0 +24,0 @@ "jasmine-console-reporter": "^1.2.7", |
# d3-scale-cluster | ||
A custom D3 scale powered by a 1-dimensional clustering algoirthm. Similar to [quantile scales](https://github.com/d3/d3-scale/blob/master/README.md#scaleQuantile), the cluster scale maps a continuous input domain to a discrete range. The number of values in the output range determines the number of clusters that will be computed from the domain. The graphic below demonstrates how cluster compares to D3's quantile and quantize scales: | ||
A custom D3 scale powered by a 1-dimensional clustering algorithm. Similar to [quantile scales](https://github.com/d3/d3-scale/blob/master/README.md#scaleQuantile), the cluster scale maps a continuous input domain to a discrete range. The number of values in the output range determines the number of clusters that will be computed from the domain. The graphic below demonstrates how cluster compares to D3's quantile and quantize scales: | ||
@@ -13,5 +13,5 @@ <img width="420" alt="d3 scale cluster example" src="https://cloud.githubusercontent.com/assets/875591/18608070/0213d7ce-7cdf-11e6-89aa-1b0e18e63cc8.png"> | ||
###Getting Started | ||
### Getting Started | ||
#####Using npm | ||
##### Using npm | ||
@@ -27,3 +27,3 @@ Install the npm package | ||
```es6 | ||
// Using ES6 imports | ||
// Using ES6 imports | ||
import scaleCluster from 'd3-scale-cluster'; | ||
@@ -35,3 +35,3 @@ | ||
#####Using a `<script>` tag | ||
##### Using a `<script>` tag | ||
@@ -49,3 +49,3 @@ Include the following script tag on your page after D3 | ||
``` | ||
###Example Usage | ||
### Example Usage | ||
@@ -64,3 +64,3 @@ This scale largely has the same API as [d3.scaleQuantile](https://github.com/d3/d3-scale/blob/master/README.md#scaleQuantile) (however we use `clusters()` instead of `quantiles()`) | ||
###API | ||
### API | ||
@@ -95,5 +95,32 @@ d3.**scaleCluster**() | ||
###Contributing | ||
_cluster_.**import**() | ||
Updates the scale with the result of a _cluster_.**export**() call. Useful for offloading computation into a webworker. | ||
_cluster_.**export**() | ||
Exports the internals of the scale as an object, for use with _cluster_.**import**(). Useful for offloading computation into a webworker. | ||
### Using in a Web Worker | ||
For data sets of significant size, you may want to offload computation into a Web Worker so that it does not block the main thread. You can use _cluster_.**import**() and _cluster_.**export**() as follows: | ||
**worker.js** | ||
```js | ||
const scale = scaleCluster().domain(domain).range(range); | ||
self.postMessage({scale: scale.export()}); | ||
``` | ||
**Main thread** | ||
```js | ||
worker.onmessage = function (event) { | ||
const scale = scaleCluster().import(event.data.scale) | ||
}; | ||
``` | ||
### Contributing | ||
``` | ||
npm install | ||
@@ -104,4 +131,10 @@ npm run test # run tests | ||
###Thanks | ||
### Thanks | ||
Thanks to Haizhou Wang and Mingzhou Song for developing the original [Ckmeans 1D clustering algorithm](https://cran.r-project.org/web/packages/Ckmeans.1d.dp/), and Tom MacWright for his [previous work](http://www.macwright.org/2013/02/18/literate-jenks.html) in bringing these techniques to the web. | ||
### Links & Resources | ||
- [Using clustering to create a new D3.js color scale](https://medium.com/@dschnr/using-clustering-to-create-a-new-d3-js-color-scale-dec4ccd639d2) - Medium post describing this project | ||
- [Choropleth with d3-scale-cluster](https://bl.ocks.org/schnerd/99767e64051096388078913afca3ff4e) - Interactive block comparing cluster, quantile, and quantize scales | ||
- [Interactive d3-scale-cluster demo](http://bl.ocks.org/tomshanley/raw/2de81c66fbe4cab9dc4e4e4c579a4d1a/) - Paste in your data to see clusters. By [@tomshanleynz](https://twitter.com/tomshanleynz) |
@@ -6,21 +6,19 @@ var Jasmine = require('jasmine'); | ||
jasmine.loadConfig({ | ||
spec_dir: 'spec', | ||
spec_files: [ | ||
'**/*[sS]pec.js' | ||
], | ||
helpers: [ | ||
'helpers/**/*.js' | ||
], | ||
stopSpecOnExpectationFailure: false, | ||
random: false | ||
spec_dir: 'spec', | ||
spec_files: ['**/*[sS]pec.js'], | ||
helpers: ['helpers/**/*.js'], | ||
stopSpecOnExpectationFailure: false, | ||
random: false | ||
}); | ||
jasmine.addReporter(new JasmineConsoleReporter({ | ||
colors: 1, // (0|false)|(1|true)|2 | ||
cleanStack: 1, // (0|false)|(1|true)|2|3 | ||
verbosity: 4, // (0|false)|1|2|(3|true)|4 | ||
listStyle: 'indent', // "flat"|"indent" | ||
activity: false | ||
})); | ||
jasmine.addReporter( | ||
new JasmineConsoleReporter({ | ||
colors: 1, // (0|false)|(1|true)|2 | ||
cleanStack: 1, // (0|false)|(1|true)|2|3 | ||
verbosity: 4, // (0|false)|1|2|(3|true)|4 | ||
listStyle: 'indent', // "flat"|"indent" | ||
activity: false | ||
}) | ||
); | ||
jasmine.execute(); |
@@ -0,1 +1,2 @@ | ||
/* globals describe, beforeEach, it, expect */ | ||
describe('Scale', function () { | ||
@@ -13,5 +14,3 @@ var d3scaleCluster = require('../src/index.js'); | ||
it('should find clusters', function () { | ||
scale | ||
.domain(DEFAULT_DOMAIN) | ||
.range(DEFAULT_RANGE); | ||
scale.domain(DEFAULT_DOMAIN).range(DEFAULT_RANGE); | ||
@@ -21,8 +20,10 @@ var clusters = scale.clusters(); | ||
expect(scale(52)).toEqual('c'); | ||
var exported = scale.export(); | ||
var newScale = d3scaleCluster().import(exported); | ||
expect(newScale(52)).toEqual('c'); | ||
}); | ||
it('should be able to invert extent', function () { | ||
scale | ||
.domain(DEFAULT_DOMAIN) | ||
.range(DEFAULT_RANGE); | ||
scale.domain(DEFAULT_DOMAIN).range(DEFAULT_RANGE); | ||
expect(scale.invertExtent('c')).toEqual([43, 123]); // Up to but not including 123 | ||
@@ -32,5 +33,3 @@ }); | ||
it('should return NaNs inverting an invalid value', function () { | ||
scale | ||
.domain(DEFAULT_DOMAIN) | ||
.range(DEFAULT_RANGE); | ||
scale.domain(DEFAULT_DOMAIN).range(DEFAULT_RANGE); | ||
expect(scale.invertExtent('lol')).toEqual([NaN, NaN]); | ||
@@ -44,7 +43,9 @@ }); | ||
it('should "gracefully" handle cases where range has more values than domain', function () { | ||
scale | ||
.domain([1, 2, 4]) | ||
.range(DEFAULT_RANGE); | ||
scale.domain([1, 2, 4]).range(DEFAULT_RANGE); | ||
expect(scale(4)).toEqual('c'); | ||
var exported = scale.export(); | ||
var newScale = d3scaleCluster().import(exported); | ||
expect(newScale(4)).toEqual('c'); | ||
}); | ||
}); |
var ckmeans = require('./ckmeans.js'); | ||
function d3scaleCluster () { | ||
var clusters = []; | ||
var isReady = false; | ||
var domain = []; | ||
@@ -10,3 +10,3 @@ var range = []; | ||
var scale = function (x) { | ||
if (clusters.length === 0) return undefined; | ||
if (!isReady) return undefined; | ||
@@ -26,4 +26,4 @@ for (var i = breakpoints.length - 1; i >= 0; i--) { | ||
clusters = ckmeans(domain, Math.min(domain.length, range.length)); | ||
var clusters = ckmeans(domain, Math.min(domain.length, range.length)); | ||
isReady = clusters.length !== 0; | ||
breakpoints = []; | ||
@@ -77,2 +77,22 @@ for (var i = 0; i < clusters.length; i++) { | ||
scale.export = function () { | ||
return { | ||
isReady: isReady, | ||
domain: domain, | ||
range: range, | ||
breakpoints: breakpoints | ||
}; | ||
}; | ||
scale.import = function (params) { | ||
if (!params) { | ||
throw new Error('Import requires parameters'); | ||
} | ||
isReady = params.isReady; | ||
domain = params.domain; | ||
range = params.range; | ||
breakpoints = params.breakpoints; | ||
return scale; | ||
}; | ||
scale.copy = function () { | ||
@@ -90,2 +110,1 @@ return d3scaleCluster().domain(domain).range(range); | ||
module.exports = d3scaleCluster; | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
25160
406
134
9