turbo-carto
Advanced tools
Comparing version 0.14.0 to 0.15.0
# Changelog | ||
## Version 0.15.0 | ||
Released 2016-07-19 | ||
- New API: `property: ramp([attribute], (...values), (...filters), mapping);`. | ||
- It's backwards compatible with previous signatures. | ||
- New `range(min, max)` function. | ||
- High level functions for quantifications: `category()`, `equal()`, `headtails()`, `jenks()`, and `quantiles()`. | ||
- Improved documentation and examples. | ||
- Removes `colors` and `buckets` function. | ||
## Version 0.14.0 | ||
@@ -4,0 +15,0 @@ Released 2016-07-06 |
@@ -1,116 +0,3 @@ | ||
# Quickstart | ||
# DEPRECATED | ||
## Color ramps: *-fill | ||
### Basic usage | ||
```css | ||
marker-fill: ramp([column_name], (red, green, blue)); | ||
| | | ||
v | | ||
column to calculate ramp | | ||
v | ||
it will use a 3 buckets color ramp as provided | ||
``` | ||
### Basic usage with colorbrewer or cartocolor | ||
```css | ||
marker-fill: ramp([column_name], colorbrewer(Greens)); | ||
| | | ||
v | | ||
column to calculate ramp | | ||
v | ||
it will use a color ramp from http://colorbrewer2.org/ | ||
``` | ||
### Change number of color brewer data classes | ||
```css | ||
marker-fill: ramp([column_name], colorbrewer(YlGnBu, 7)); | ||
| | ||
v | ||
force number of classes | ||
default: 5 classes | ||
``` | ||
### Change quantification method | ||
```css | ||
marker-fill: ramp([column_name], colorbrewer(Reds), jenks); | ||
| | ||
v | ||
force quantification method | ||
default: quantiles | ||
``` | ||
## Numeric ramps: *-width, *-opacity | ||
### Basic usage | ||
```css | ||
marker-width: ramp([column_name], (4, 8, 16, 32)); | ||
| | | ||
v | | ||
column to calculate ramp | | ||
| | ||
| | ||
| | ||
v | ||
provide the steps for the symbol sizes | ||
``` | ||
### Basic usage with interpolation for symbol size | ||
```css | ||
marker-width: ramp([column_name], 4, 18); | ||
| | | | ||
v | | | ||
column to calculate ramp | | | ||
v | | ||
start value for the ramp | | ||
| | ||
v | ||
end value for the ramp | ||
``` | ||
### Change quantification method | ||
```css | ||
marker-width: ramp([column_name], 4, 18, equal); | ||
| | ||
v | ||
force quantification method | ||
default: quantiles | ||
``` | ||
### Change number of buckets | ||
```css | ||
marker-width: ramp([column_name], 4, 18, 6); | ||
| | ||
v | ||
force number of buckets | ||
default: 5 | ||
``` | ||
### Change both: number of buckets and quantification method | ||
```css | ||
marker-width: ramp([column_name], 4, 18, 6, jenks); | ||
| | | ||
v | | ||
force number of classes | | ||
v | ||
force quantification method | ||
``` | ||
## Options | ||
### Quantification methods | ||
- quantiles: as per [`CDB_QuantileBins`](https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_QuantileBins.sql). | ||
- equal: as per [`CDB_EqualIntervalBins`](https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_EqualIntervalBins.sql). | ||
- jenks: as per [`CDB_JenksBins`](https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_JenksBins.sql). | ||
- headtails: as per [`CDB_HeadsTailsBins`](https://github.com/CartoDB/cartodb-postgresql/blob/master/scripts-available/CDB_HeadsTailsBins.sql). | ||
Check [README.md](../README.md) for API and examples. |
{ | ||
"name": "turbo-carto", | ||
"version": "0.14.0", | ||
"version": "0.15.0", | ||
"description": "CartoCSS preprocessor", | ||
@@ -5,0 +5,0 @@ "main": "src/index.js", |
170
README.md
@@ -1,4 +0,4 @@ | ||
# Turbo Carto | ||
# Turbo-Carto | ||
AKA CartoCSS preprocessor | ||
Next-Gen Styling for Data-Driven Maps, AKA CartoCSS preprocessor | ||
@@ -9,8 +9,166 @@ tl;dr Enables adding functions to CartoCSS that can be evaluated asynchronously. | ||
TBC. | ||
## Ramps | ||
## Limitations | ||
It's all about ramps. | ||
- TBA | ||
You can create color and symbol size ramps with just a single line of code. You no longer need to worry about | ||
calculating the bins for your thematic map, if your data changes your CartoCSS changes. | ||
### Very basic introduction to CartoCSS | ||
In CartoCSS you usually assign values to properties, and apply filters in order to change those values based on some | ||
data attributes. | ||
The general form for properties and filters is: | ||
```css | ||
#selector { | ||
property: value; | ||
[filter] { | ||
property: value; | ||
} | ||
} | ||
``` | ||
An example for a filter based on price attribute: | ||
```css | ||
#selector { | ||
marker-width: 10; | ||
[price > 100] { | ||
marker-width: 20; | ||
} | ||
} | ||
``` | ||
### Turbo-Carto ramps | ||
Turbo-Carto high level API for ramps is as follows: | ||
```css | ||
#selector { | ||
property: ramp([attribute], (...values), (...filters), mapping); | ||
} | ||
``` | ||
Where: | ||
- `property` is the CartoCSS property you want to create. | ||
- `[attribute]` usually is a column/key name from your dataset. | ||
- `(...values)` is **what** `property` is gonna get for different filters. | ||
- `(...filters)` is **how** `property` is gonna get the different values. | ||
- `mapping` is the type of filter that will be applied: <, >, =. | ||
So for the previous example you could write (see [examples/readme/example-0.css](./examples/readme/example-0.css)): | ||
```css | ||
#selector { | ||
marker-width: ramp([price], (10, 20), (100)); | ||
} | ||
``` | ||
In this case the first value is the default value. | ||
If you want to have a category map, to generate a CartoCSS like: | ||
```css | ||
#selector { | ||
marker-fill: red; | ||
[room_type = "Private Room"] { | ||
marker-fill: green; | ||
} | ||
} | ||
``` | ||
You can use the same approach but specifying the mapping type to be an equality (see [examples/readme/example-1.css](./examples/readme/example-1.css)): | ||
```css | ||
#selector { | ||
marker-width: ramp([room_type], (green, red), ("Private room"), =); | ||
} | ||
``` | ||
See that in this case the last value is the default value, and if the number of values is equal to the number of filters | ||
it won't have a default value, like in (see [examples/readme/example-2.css](./examples/readme/example-2.css)): | ||
```css | ||
#selector { | ||
marker-width: ramp([room_type], (green, red), ("Private room"), =); | ||
} | ||
``` | ||
That's nice, but it is still unlinked from the actual data/attributes. | ||
#### Associate ramp filters to your data | ||
To generate ramps based on actual data you have to say what kind of quantification you want to use, for that purpose | ||
Turbo-Carto provides some shorthand methods that will delegate the filters computation to different collaborators. | ||
Let's say you want to compute the ramp using jenks as quantification function (see [examples/readme/example-3.css](./examples/readme/example-3.css)): | ||
```css | ||
#selector { | ||
marker-width: ramp([price], (10, 20, 30), jenks()); | ||
} | ||
``` | ||
Or generate a category map depending on the room_type property (see [examples/readme/example-4.css](./examples/readme/example-4.css)): | ||
```css | ||
#selector { | ||
marker-fill: ramp([room_type], (red, green, blue), category(2)); | ||
} | ||
``` | ||
You can override the mapping if you know your data requires an more strict filter (see [examples/readme/example-5.css](./examples/readme/example-5.css)): | ||
```css | ||
#selector { | ||
marker-width: ramp([price], (10, 20, 30), jenks(), >=); | ||
} | ||
``` | ||
Shorthand methods include: | ||
- `category()`: default mapping is `=`. | ||
- `equal()`: default mapping is `>`. | ||
- `headtails()`: default mapping is `<`. | ||
- `jenks()`: default mapping is `>`. | ||
- `quantiles()`: default mapping is `>`. | ||
#### Color ramps | ||
For color ramps there are a couple of handy functions to retrieve color palettes: `cartocolor` and `colorbrewer`. | ||
You can create a choropleth map using Reds color palette from colorbrewer (see [examples/readme/example-6.css](./examples/readme/example-6.css)): | ||
```css | ||
#selector { | ||
polygon-fill: ramp([avg_price], colorbrewer(Reds), jenks()); | ||
} | ||
``` | ||
Or a category map using Bold color palette from cartocolor (see [examples/readme/example-7.css](./examples/readme/example-7.css)): | ||
```css | ||
#selector { | ||
polygon-fill: ramp([room_type], cartocolor(Bold), category(4)); | ||
} | ||
``` | ||
#### Numeric ramps | ||
Sometimes is really useful to have a continuous range that can be split in as many bins as desired, so you don't have to | ||
worry about how many values you have to provide for your calculated filters. You only specify the start and the end, and | ||
the values are computed linearly (see [examples/readme/example-8.css](./examples/readme/example-8.css)): | ||
```css | ||
#selector { | ||
marker-width: ramp([price], range(4, 40), equal(5)); | ||
} | ||
``` | ||
It is kind of similar to color palettes where depending on your number of filters you get the correct number of values. | ||
## Dependencies | ||
@@ -41,3 +199,3 @@ | ||
Visit `examples/index.html`, it also . | ||
Visit `examples/index.html`, to test different styles using CartoDB's SQL API as datasource. | ||
@@ -44,0 +202,0 @@ ## Tests |
@@ -5,3 +5,4 @@ 'use strict'; | ||
var debug = require('../helper/debug')('fn-factory'); | ||
var debug = require('../helper/debug')('fn-anonymous-tuple'); | ||
var ValuesResult = require('../model/values-result'); | ||
@@ -14,3 +15,3 @@ module.exports = function () { | ||
var tupleValues = Object.keys(args).map(function (k) { return args[k]; }); | ||
resolve(tupleValues); | ||
resolve(new ValuesResult(tupleValues)); | ||
}); | ||
@@ -17,0 +18,0 @@ }; |
@@ -5,54 +5,34 @@ 'use strict'; | ||
var debug = require('../helper/debug')('fn-buckets'); | ||
var columnName = require('../helper/column-name'); | ||
var debug = require('../helper/debug')('fn-factory'); | ||
var postcss = require('postcss'); | ||
var TurboCartoError = require('../helper/turbo-carto-error'); | ||
var FiltersResult = require('../model/filters-result'); | ||
module.exports = function (datasource, decl) { | ||
return function fn$buckets (column, scheme) { | ||
module.exports = function (datasource) { | ||
return function fn$buckets (column, quantificationMethod, numBuckets) { | ||
debug('fn$buckets(%j)', arguments); | ||
debug('Using "%s" datasource to calculate buckets', datasource.getName()); | ||
return getRamp(datasource, column) | ||
.then(function (ramp) { | ||
var i; | ||
var rampResult = []; | ||
rampResult.push(scheme[0]); | ||
for (i = 0; i < 5; i++) { | ||
rampResult.push(ramp[i]); | ||
rampResult.push(scheme[i]); | ||
return new Promise(function (resolve, reject) { | ||
if (!quantificationMethod) { | ||
return reject(new TurboCartoError('Missing quantification method in buckets function')); | ||
} | ||
numBuckets = Number.isFinite(+numBuckets) ? +numBuckets : 5; | ||
datasource.getRamp(columnName(column), numBuckets, quantificationMethod, function (err, filters) { | ||
if (err) { | ||
return reject( | ||
new TurboCartoError('unable to compute ramp,', err) | ||
); | ||
} | ||
var parent = decl.parent; | ||
var start = rampResult.shift(); | ||
column = columnName(column); | ||
rampResult = rampResult.reverse(); | ||
parent.append(postcss.decl({ prop: decl.prop, value: start })); | ||
for (i = 0; i < rampResult.length; i += 2) { | ||
var rule = postcss.rule({ | ||
selector: '[ ' + column + ' < ' + rampResult[i + 1] + ' ]' | ||
}); | ||
rule.append(postcss.decl({prop: decl.prop, value: rampResult[i]})); | ||
parent.append(rule); | ||
var strategy = 'max'; | ||
if (!Array.isArray(filters)) { | ||
strategy = filters.strategy || 'max'; | ||
filters = filters.ramp; | ||
} | ||
decl.remove(); | ||
resolve(new FiltersResult(filters, strategy)); | ||
}); | ||
}); | ||
}; | ||
}; | ||
function getRamp (datasource, column, method) { | ||
return new Promise(function (resolve, reject) { | ||
datasource.getRamp(columnName(column), method || 'quantiles', function (err, ramp) { | ||
if (err) { | ||
return reject(err); | ||
} | ||
resolve(ramp); | ||
}); | ||
}); | ||
} | ||
module.exports.fnName = 'buckets'; |
@@ -6,3 +6,5 @@ 'use strict'; | ||
var cartocolor = require('cartocolor'); | ||
var debug = require('../helper/debug')('fn-factory'); | ||
var ValuesResult = require('../model/values-result'); | ||
var minMaxKeys = require('../helper/min-max-keys'); | ||
var debug = require('../helper/debug')('fn-cartocolor'); | ||
@@ -12,10 +14,10 @@ module.exports = function () { | ||
debug('fn$cartocolor(%j)', arguments); | ||
numberDataClasses = Math.min(7, Math.max(3, numberDataClasses || 5)); | ||
return new Promise(function (resolve) { | ||
var result = cartocolor.BluGrn[numberDataClasses]; | ||
var def = cartocolor[scheme]; | ||
if (def) { | ||
result = def[numberDataClasses]; | ||
return new Promise(function (resolve, reject) { | ||
if (!cartocolor.hasOwnProperty(scheme)) { | ||
return reject(new Error('Invalid cartocolor scheme: "' + scheme + '"')); | ||
} | ||
resolve(result); | ||
var result = cartocolor[scheme]; | ||
var minMax = minMaxKeys(result); | ||
numberDataClasses = Math.min(minMax.max, Math.max(minMax.min, numberDataClasses || 5)); | ||
resolve(new ValuesResult(result, numberDataClasses, null, minMax.max)); | ||
}); | ||
@@ -22,0 +24,0 @@ }; |
@@ -5,3 +5,3 @@ 'use strict'; | ||
var debug = require('../helper/debug')('fn-factory'); | ||
var debug = require('../helper/debug')('fn-identity'); | ||
@@ -8,0 +8,0 @@ module.exports = function (fnName) { |
@@ -5,6 +5,11 @@ 'use strict'; | ||
var debug = require('../helper/debug')('fn-factory'); | ||
var debug = require('../helper/debug')('fn-ramp'); | ||
var columnName = require('../helper/column-name'); | ||
var TurboCartoError = require('../helper/turbo-carto-error'); | ||
var buckets = require('../helper/linear-buckets'); | ||
var isResult = require('../model/is-result'); | ||
var linearBuckets = require('../helper/linear-buckets'); | ||
var ValuesResult = require('../model/values-result'); | ||
var FiltersResult = require('../model/filters-result'); | ||
var LazyFiltersResult = require('../model/lazy-filters-result'); | ||
var RampResult = require('../model/ramp/ramp-result'); | ||
var postcss = require('postcss'); | ||
@@ -58,3 +63,3 @@ | ||
exact: createSplitStrategy(function exactSelector (column, value) { | ||
return '[ ' + column + ' = "' + value + '" ]'; | ||
return Number.isFinite(value) ? '[ ' + column + ' = ' + value + ' ]' : '[ ' + column + ' = "' + value + '" ]'; | ||
}) | ||
@@ -72,2 +77,5 @@ }; | ||
.then(function (rampResult) { | ||
if (rampResult.constructor === RampResult) { | ||
return rampResult.process(columnName(column), decl); | ||
} | ||
var strategyFn = strategy.hasOwnProperty(rampResult.strategy) ? strategy[rampResult.strategy] : strategy.max; | ||
@@ -124,6 +132,2 @@ return strategyFn(columnName(column), rampResult.ramp, decl); | ||
function ramp (datasource, column, args) { | ||
var method; | ||
var tuple = []; | ||
if (args.length === 0) { | ||
@@ -135,27 +139,128 @@ return Promise.reject( | ||
if (Array.isArray(args[0])) { | ||
tuple = args[0]; | ||
method = args[1]; | ||
/** | ||
* Overload scenarios to support | ||
* marker-width: ramp([price], 4, 100); | ||
* marker-width: ramp([price], 4, 100, method); | ||
* marker-width: ramp([price], 4, 100, 5, method); | ||
* marker-width: ramp([price], 4, 100, 3, (100, 200, 1000)); | ||
* marker-width: ramp([price], 4, 100, (100, 150, 250, 200, 1000)); | ||
*/ | ||
if (Number.isFinite(args[0])) { | ||
return compatibilityNumericRamp(datasource, column, args); | ||
} | ||
/** | ||
* Overload methods to support | ||
* marker-fill: ramp([price], colorbrewer(Reds)); | ||
* marker-fill: ramp([price], colorbrewer(Reds), jenks); | ||
*/ | ||
if (!isResult(args[1])) { | ||
return compatibilityValuesRamp(datasource, column, args); | ||
} | ||
/** | ||
* Overload methods to support from here | ||
* marker-fill: ramp([price], colorbrewer(Reds), (100, 200, 300, 400, 500)); | ||
* marker-fill: ramp([price], colorbrewer(Reds), (100, 200, 300, 400, 500), =); | ||
* marker-fill: ramp([price], (...values), (...filters), [mapping]); | ||
*/ | ||
var values = args[0]; | ||
var filters = args[1]; | ||
var mapping = args[2]; | ||
var strategy = strategyFromMapping(mapping); | ||
filters = filters.is(ValuesResult) ? new FiltersResult(filters.get(), strategy) : filters; | ||
if (filters.is(LazyFiltersResult)) { | ||
return filters.get(column, strategy).then(createRampFn(values)); | ||
} else { | ||
if (args.length < 2) { | ||
return Promise.reject( | ||
new TurboCartoError('invalid number of arguments') | ||
); | ||
} | ||
return Promise.resolve(filters).then(createRampFn(values)); | ||
} | ||
} | ||
var min = +args[0]; | ||
var max = +args[1]; | ||
var oldMappings2Strategies = { | ||
quantiles: 'max', | ||
equal: 'max', | ||
jenks: 'max', | ||
headtails: 'split', | ||
category: 'exact' | ||
}; | ||
var numBuckets = 5; | ||
method = args[2]; | ||
function strategyFromMapping (mapping) { | ||
if (oldMappings2Strategies.hasOwnProperty(mapping)) { | ||
return oldMappings2Strategies[mapping]; | ||
} | ||
return mapping; | ||
} | ||
if (Number.isFinite(+args[2])) { | ||
numBuckets = +args[2]; | ||
/** | ||
* Overload methods to support | ||
* marker-fill: ramp([price], colorbrewer(Reds)); | ||
* marker-fill: ramp([price], colorbrewer(Reds), jenks); | ||
*/ | ||
function compatibilityValuesRamp (datasource, column, args) { | ||
var values = args[0]; | ||
var method = (args[1] || 'quantiles').toLowerCase(); | ||
var numBuckets = values.getLength(); | ||
return getRamp(datasource, column, numBuckets, method).then(compatibilityCreateRampFn(values)); | ||
} | ||
/** | ||
* Overload scenarios to support | ||
* marker-width: ramp([price], 4, 100); | ||
* marker-width: ramp([price], 4, 100, method); | ||
* marker-width: ramp([price], 4, 100, 5, method); √ | ||
* marker-width: ramp([price], 4, 100, 3, (100, 200, 1000)); √ | ||
* marker-width: ramp([price], 4, 100, (100, 150, 250, 200, 1000)); √ | ||
*/ | ||
function compatibilityNumericRamp (datasource, column, args) { | ||
// jshint maxcomplexity:9 | ||
if (args.length < 2) { | ||
return Promise.reject( | ||
new TurboCartoError('invalid number of arguments') | ||
); | ||
} | ||
var min = +args[0]; | ||
var max = +args[1]; | ||
var numBuckets; | ||
var filters = null; | ||
var method; | ||
if (Number.isFinite(args[2])) { | ||
numBuckets = args[2]; | ||
if (isResult(args[3])) { | ||
filters = args[3]; | ||
method = null; | ||
if (filters.getLength() !== numBuckets) { | ||
return Promise.reject( | ||
new TurboCartoError( | ||
'invalid ramp length, got ' + filters.getLength() + ' values, expected ' + numBuckets | ||
) | ||
); | ||
} | ||
} else { | ||
filters = null; | ||
method = args[3]; | ||
} | ||
} else if (isResult(args[2])) { | ||
filters = args[2]; | ||
numBuckets = filters.getLength(); | ||
method = null; | ||
} else { | ||
filters = null; | ||
numBuckets = 5; | ||
method = args[2]; | ||
} | ||
tuple = buckets(min, max, numBuckets); | ||
var values = new ValuesResult([min, max], numBuckets, linearBuckets, Number.POSITIVE_INFINITY); | ||
if (filters === null) { | ||
// normalize method | ||
method = (method || 'quantiles').toLowerCase(); | ||
return getRamp(datasource, column, numBuckets, method).then(compatibilityCreateRampFn(values)); | ||
} | ||
return tupleRamp(datasource, column, tuple, method); | ||
filters = filters.is(FiltersResult) ? filters : new FiltersResult(filters.get(), 'max'); | ||
return Promise.resolve(filters).then(compatibilityCreateRampFn(values)); | ||
} | ||
@@ -165,3 +270,3 @@ | ||
return new Promise(function (resolve, reject) { | ||
datasource.getRamp(columnName(column), buckets, method, function (err, ramp) { | ||
datasource.getRamp(columnName(column), buckets, method, function (err, filters) { | ||
if (err) { | ||
@@ -172,3 +277,8 @@ return reject( | ||
} | ||
resolve(ramp); | ||
var strategy = 'max'; | ||
if (!Array.isArray(filters)) { | ||
strategy = filters.strategy || 'max'; | ||
filters = filters.ramp; | ||
} | ||
resolve(new FiltersResult(filters, strategy)); | ||
}); | ||
@@ -178,31 +288,32 @@ }); | ||
function tupleRamp (datasource, column, tuple, method) { | ||
if (Array.isArray(method)) { | ||
var ramp = method; | ||
if (tuple.length !== ramp.length) { | ||
return Promise.reject( | ||
new TurboCartoError('invalid ramp length, got ' + ramp.length + ' values, expected ' + tuple.length) | ||
); | ||
function compatibilityCreateRampFn (valuesResult) { | ||
return function prepareRamp (filtersResult) { | ||
var buckets = Math.min(valuesResult.getLength(), filtersResult.getLength()); | ||
var i; | ||
var rampResult = []; | ||
var filters = filtersResult.get(); | ||
var values = valuesResult.get(); | ||
if (buckets > 0) { | ||
for (i = 0; i < buckets; i++) { | ||
rampResult.push(filters[i]); | ||
rampResult.push(values[i]); | ||
} | ||
} else { | ||
rampResult.push(null, values[0]); | ||
} | ||
var strategy = ramp.map(function numberMapper (n) { return +n; }).every(Number.isFinite) ? 'split' : 'exact'; | ||
return Promise.resolve({ramp: ramp, strategy: strategy}).then(createRampFn(tuple)); | ||
} | ||
// normalize method | ||
if (method) { | ||
method = method.toLowerCase(); | ||
} | ||
return getRamp(datasource, column, tuple.length, method).then(createRampFn(tuple)); | ||
return { ramp: rampResult, strategy: filtersResult.getStrategy() }; | ||
}; | ||
} | ||
function createRampFn (tuple) { | ||
return function prepareRamp (ramp) { | ||
var strategy = 'max'; | ||
if (!Array.isArray(ramp)) { | ||
strategy = ramp.strategy || 'max'; | ||
ramp = ramp.ramp; | ||
function createRampFn (valuesResult) { | ||
return function prepareRamp (filtersResult) { | ||
if (RampResult.supports(filtersResult.getStrategy())) { | ||
return new RampResult(valuesResult, filtersResult, filtersResult.getStrategy()); | ||
} | ||
var buckets = Math.min(tuple.length, ramp.length); | ||
var buckets = Math.min(valuesResult.getMaxSize(), filtersResult.getMaxSize()); | ||
@@ -212,12 +323,15 @@ var i; | ||
var filters = filtersResult.get(); | ||
var values = valuesResult.get(buckets); | ||
if (buckets > 0) { | ||
for (i = 0; i < buckets; i++) { | ||
rampResult.push(ramp[i]); | ||
rampResult.push(tuple[i]); | ||
rampResult.push(filters[i]); | ||
rampResult.push(values[i]); | ||
} | ||
} else { | ||
rampResult.push(null, tuple[0]); | ||
rampResult.push(null, values[0]); | ||
} | ||
return { ramp: rampResult, strategy: strategy }; | ||
return { ramp: rampResult, strategy: filtersResult.getStrategy() }; | ||
}; | ||
@@ -224,0 +338,0 @@ } |
'use strict'; | ||
module.exports = function (min, max, numBuckets) { | ||
if (Array.isArray(min)) { | ||
numBuckets = max; | ||
max = min[1]; | ||
min = min[0]; | ||
} | ||
var buckets = []; | ||
var range = max - min; | ||
var width = range / (numBuckets - 1); | ||
if (width === Number.POSITIVE_INFINITY || width === Number.NEGATIVE_INFINITY) { | ||
width = 0; | ||
} | ||
for (var i = 0; i < numBuckets; i++) { | ||
@@ -8,0 +16,0 @@ buckets.push(min + i * width); |
@@ -7,3 +7,3 @@ 'use strict'; | ||
var postcss = require('postcss'); | ||
var FnBuilder = require('./fn/fn-builder'); | ||
var FnBuilder = require('./fn/builder'); | ||
@@ -10,0 +10,0 @@ function PostcssTurboCarto (datasource) { |
'use strict'; | ||
var assert = require('assert'); | ||
var postcss = require('postcss'); | ||
var PostcssTurboCarto = require('../../src/postcss-turbo-carto'); | ||
var turbocarto = require('../../src/index'); | ||
var DummyDatasource = require('../support/dummy-datasource'); | ||
@@ -20,16 +19,5 @@ var DummyStrategyDatasource = require('../support/dummy-strategy-datasource'); | ||
}); | ||
var numericExactStrategyDatasource = new DummyStrategyDatasource('exact'); | ||
describe('ramp-strategy', function () { | ||
function getCartoCss (datasource, cartocss, callback) { | ||
var postcssTurboCarto = new PostcssTurboCarto(datasource); | ||
postcss([postcssTurboCarto.getPlugin()]) | ||
.process(cartocss) | ||
.then(function (result) { | ||
return callback(null, result.css); | ||
}) | ||
.catch(function (err) { | ||
return callback(err); | ||
}); | ||
} | ||
var cartocss = [ | ||
@@ -113,2 +101,151 @@ '#layer{', | ||
].join('\n') | ||
}, | ||
{ | ||
desc: 'result with exact strategy generates uses numeric values', | ||
datasource: numericExactStrategyDatasource, | ||
cartocss: [ | ||
'#layer{', | ||
' marker-width: ramp([population], (10, 20, 30, 40));', | ||
'}' | ||
].join('\n'), | ||
expectedCartocss: [ | ||
'#layer{', | ||
' marker-width: 10;', | ||
' [ population = 1 ]{', | ||
' marker-width: 20', | ||
' }', | ||
' [ population = 2 ]{', | ||
' marker-width: 30', | ||
' }', | ||
' [ population = 3 ]{', | ||
' marker-width: 40', | ||
' }', | ||
'}' | ||
].join('\n') | ||
}, | ||
{ | ||
desc: 'result with exact strategy is generated from buckets fn', | ||
datasource: numericExactStrategyDatasource, | ||
cartocss: [ | ||
'#layer{', | ||
' marker-width: ramp([population], range(10, 50), buckets([population], category));', | ||
'}' | ||
].join('\n'), | ||
expectedCartocss: [ | ||
'#layer{', | ||
' marker-width: 10;', | ||
' [ population = 1 ]{', | ||
' marker-width: 20', | ||
' }', | ||
' [ population = 2 ]{', | ||
' marker-width: 30', | ||
' }', | ||
' [ population = 3 ]{', | ||
' marker-width: 40', | ||
' }', | ||
' [ population = 4 ]{', | ||
' marker-width: 50', | ||
' }', | ||
'}' | ||
].join('\n') | ||
}, | ||
{ | ||
desc: 'result with exact strategy is generated from buckets fn, force number of buckets', | ||
datasource: numericExactStrategyDatasource, | ||
cartocss: [ | ||
'#layer{', | ||
' marker-width: ramp([population], range(10, 40), buckets([population], category, 4));', | ||
'}' | ||
].join('\n'), | ||
expectedCartocss: [ | ||
'#layer{', | ||
' marker-width: 10;', | ||
' [ population = 1 ]{', | ||
' marker-width: 20', | ||
' }', | ||
' [ population = 2 ]{', | ||
' marker-width: 30', | ||
' }', | ||
' [ population = 3 ]{', | ||
' marker-width: 40', | ||
' }', | ||
'}' | ||
].join('\n') | ||
}, | ||
{ | ||
desc: 'result with exact strategy is generated from category fn', | ||
datasource: numericExactStrategyDatasource, | ||
cartocss: [ | ||
'#layer{', | ||
' marker-width: ramp([population], range(10, 50), category());', | ||
'}' | ||
].join('\n'), | ||
expectedCartocss: [ | ||
'#layer{', | ||
' marker-width: 50;', | ||
' [ population = 0 ]{', | ||
' marker-width: 10', | ||
' }', | ||
' [ population = 1 ]{', | ||
' marker-width: 18', | ||
' }', | ||
' [ population = 2 ]{', | ||
' marker-width: 26', | ||
' }', | ||
' [ population = 3 ]{', | ||
' marker-width: 34', | ||
' }', | ||
' [ population = 4 ]{', | ||
' marker-width: 42', | ||
' }', | ||
'}' | ||
].join('\n') | ||
}, | ||
{ | ||
desc: 'result with exact strategy is generated from category fn, force number of buckets', | ||
datasource: numericExactStrategyDatasource, | ||
cartocss: [ | ||
'#layer{', | ||
' marker-width: ramp([population], range(10, 40), category(3));', | ||
'}' | ||
].join('\n'), | ||
expectedCartocss: [ | ||
'#layer{', | ||
' marker-width: 40;', | ||
' [ population = 0 ]{', | ||
' marker-width: 10', | ||
' }', | ||
' [ population = 1 ]{', | ||
' marker-width: 20', | ||
' }', | ||
' [ population = 2 ]{', | ||
' marker-width: 30', | ||
' }', | ||
'}' | ||
].join('\n') | ||
}, | ||
{ | ||
desc: 'should not set default value for equal mapping when values and filters are same size', | ||
datasource: numericExactStrategyDatasource, | ||
cartocss: [ | ||
'#layer{', | ||
' marker-width: ramp([population], (10, 20, 30, 40), (0, 1, 2, 3), =);', | ||
'}' | ||
].join('\n'), | ||
expectedCartocss: [ | ||
'#layer{', | ||
' [ population = 0 ]{', | ||
' marker-width: 10', | ||
' }', | ||
' [ population = 1 ]{', | ||
' marker-width: 20', | ||
' }', | ||
' [ population = 2 ]{', | ||
' marker-width: 30', | ||
' }', | ||
' [ population = 3 ]{', | ||
' marker-width: 40', | ||
' }', | ||
'}' | ||
].join('\n') | ||
} | ||
@@ -118,4 +255,5 @@ ]; | ||
scenarios.forEach(function (scenario) { | ||
it(scenario.desc, function (done) { | ||
getCartoCss(scenario.datasource, scenario.cartocss || cartocss, function (err, cartocssResult) { | ||
var itFn = scenario.only ? it.only : it; | ||
itFn(scenario.desc, function (done) { | ||
turbocarto(scenario.cartocss || cartocss, scenario.datasource, function (err, cartocssResult) { | ||
if (err) { | ||
@@ -122,0 +260,0 @@ return done(err); |
@@ -5,4 +5,3 @@ 'use strict'; | ||
var assert = require('assert'); | ||
var postcss = require('postcss'); | ||
var PostcssTurboCarto = require('../../src/postcss-turbo-carto'); | ||
var turbocarto = require('../../src/index'); | ||
var DummyDatasource = require('../support/dummy-datasource'); | ||
@@ -12,4 +11,2 @@ | ||
var postcssTurboCarto = new PostcssTurboCarto(datasource); | ||
var scenariosPath = __dirname + '/scenarios'; | ||
@@ -35,16 +32,5 @@ var scenarios = fs.readdirSync(scenariosPath) | ||
describe('ramp', function () { | ||
function getCartoCss (cartocss, callback) { | ||
postcss([postcssTurboCarto.getPlugin()]) | ||
.process(cartocss) | ||
.then(function (result) { | ||
return callback(null, result.css); | ||
}) | ||
.catch(function (err) { | ||
return callback(err); | ||
}); | ||
} | ||
scenarios.forEach(function (scenario) { | ||
it(scenario.desc, function (done) { | ||
getCartoCss(scenario.cartocss, function (err, cartocssResult) { | ||
turbocarto(scenario.cartocss, datasource, function (err, cartocssResult) { | ||
if (err) { | ||
@@ -51,0 +37,0 @@ return done(err); |
'use strict'; | ||
var assert = require('assert'); | ||
var postcss = require('postcss'); | ||
var PostcssTurboCarto = require('../../src/postcss-turbo-carto'); | ||
var turbocarto = require('../../src/index'); | ||
var DummyDatasource = require('../support/dummy-datasource'); | ||
@@ -10,26 +9,30 @@ var DummyStrategyDatasource = require('../support/dummy-strategy-datasource'); | ||
describe('regressions', function () { | ||
function getCartoCss (cartocss, datasource, callback) { | ||
if (!callback) { | ||
callback = datasource; | ||
datasource = new DummyDatasource(); | ||
} | ||
datasource = datasource || new DummyDatasource(); | ||
var postcssTurboCarto = new PostcssTurboCarto(datasource); | ||
postcss([postcssTurboCarto.getPlugin()]) | ||
.process(cartocss) | ||
.then(function (result) { | ||
return callback(null, result.css); | ||
}) | ||
.catch(function (err) { | ||
return callback(err); | ||
}); | ||
} | ||
var scenarios = [ | ||
{ | ||
desc: 'should keep working with category quantification', | ||
cartocss: [ | ||
'#layer{', | ||
' marker-width: ramp([population], (10, 20, 30, 40), (_, Spain, Portugal, France), category);', | ||
'}' | ||
].join('\n'), | ||
expectedCartocss: [ | ||
'#layer{', | ||
' marker-width: 10;', | ||
' [ population = "Spain" ]{', | ||
' marker-width: 20', | ||
' }', | ||
' [ population = "Portugal" ]{', | ||
' marker-width: 30', | ||
' }', | ||
' [ population = "France" ]{', | ||
' marker-width: 40', | ||
' }', | ||
'}' | ||
].join('\n') | ||
}, | ||
{ | ||
desc: 'should use strings for filters', | ||
cartocss: [ | ||
'#layer{', | ||
' marker-width: ramp([population], (10, 20, 30, 40), (_, Spain, Portugal, France));', | ||
' marker-width: ramp([population], (10, 20, 30, 40), (_, Spain, Portugal, France), category);', | ||
'}' | ||
@@ -56,3 +59,3 @@ ].join('\n'), | ||
'#layer{', | ||
' marker-width: ramp([population], (10, 20, 30, 40), (_, "Spain\'s", Portugal, France));', | ||
' marker-width: ramp([population], (10, 20, 30, 40), (_, "Spain\'s", Portugal, France), category);', | ||
'}' | ||
@@ -136,3 +139,3 @@ ].join('\n'), | ||
' marker-fill: #fee5d9;', | ||
' [ population = "2" ]{', | ||
' [ population = 2 ]{', | ||
' marker-fill: #fcbba1', | ||
@@ -142,2 +145,21 @@ ' }', | ||
].join('\n') | ||
}, | ||
{ | ||
desc: 'should work with less values than filters', | ||
cartocss: [ | ||
'#layer{', | ||
' marker-width: ramp([population], (8, 24, 96), (8, 24, 96, 128));', | ||
'}' | ||
].join('\n'), | ||
expectedCartocss: [ | ||
'#layer{', | ||
' marker-width: 8;', | ||
' [ population > 8 ]{', | ||
' marker-width: 24', | ||
' }', | ||
' [ population > 24 ]{', | ||
' marker-width: 96', | ||
' }', | ||
'}' | ||
].join('\n') | ||
} | ||
@@ -147,4 +169,6 @@ ]; | ||
scenarios.forEach(function (scenario) { | ||
it(scenario.desc, function (done) { | ||
getCartoCss(scenario.cartocss, scenario.datasource, function (err, cartocssResult) { | ||
var itFn = scenario.only ? it.only : it; | ||
itFn(scenario.desc, function (done) { | ||
var datasource = scenario.datasource || new DummyDatasource(); | ||
turbocarto(scenario.cartocss, datasource, function (err, cartocssResult) { | ||
if (err) { | ||
@@ -151,0 +175,0 @@ return done(err); |
'use strict'; | ||
var assert = require('assert'); | ||
var FnExecutor = require('../../src/fn/fn-executor'); | ||
var FnExecutor = require('../../src/fn/executor'); | ||
var DummyDatasource = require('../support/dummy-datasource'); | ||
@@ -6,0 +6,0 @@ var postcss = require('postcss'); |
'use strict'; | ||
function DummyDatasource () { | ||
function DummyDatasource (getter) { | ||
this.getter = getter || null; | ||
} | ||
@@ -13,2 +14,6 @@ | ||
DummyDatasource.prototype.getRamp = function (column, buckets, method, callback) { | ||
if (this.getter) { | ||
var result = this.getter(column, buckets, method); | ||
return callback(null, result); | ||
} | ||
var ramp = []; | ||
@@ -15,0 +20,0 @@ for (var i = 0; i < buckets; i++) { |
@@ -10,3 +10,3 @@ 'use strict'; | ||
fn('red', 'green', 'blue').then(function (result) { | ||
assert.deepEqual(result, [ 'red', 'green', 'blue' ]); | ||
assert.deepEqual(result.get(), [ 'red', 'green', 'blue' ]); | ||
done(); | ||
@@ -18,3 +18,3 @@ }); | ||
fn(9, 8, 7).then(function (result) { | ||
assert.deepEqual(result, [ 9, 8, 7 ]); | ||
assert.deepEqual(result.get(), [ 9, 8, 7 ]); | ||
done(); | ||
@@ -21,0 +21,0 @@ }); |
@@ -10,4 +10,4 @@ 'use strict'; | ||
it('should return by default 5 data classes', function (done) { | ||
fn('Green2').then(function (result) { | ||
assert.equal(result.length, 5); | ||
fn('BluGrn').then(function (result) { | ||
assert.equal(result.get().length, 5); | ||
done(); | ||
@@ -18,4 +18,4 @@ }); | ||
it('should return at least 3 data classes', function (done) { | ||
fn('Green2', 1).then(function (result) { | ||
assert.equal(result.length, 3); | ||
fn('Peach', 1).then(function (result) { | ||
assert.equal(result.get().length, 3); | ||
done(); | ||
@@ -26,4 +26,4 @@ }); | ||
it('should return at most 7 data classes', function (done) { | ||
fn('Green2', 9).then(function (result) { | ||
assert.equal(result.length, 7); | ||
fn('RedOr', 9).then(function (result) { | ||
assert.equal(result.get().length, 7); | ||
done(); | ||
@@ -35,4 +35,4 @@ }); | ||
it('should return the correct number of data classes for param=' + numberDataClasses, function (done) { | ||
fn('Green2', numberDataClasses).then(function (result) { | ||
assert.equal(result.length, numberDataClasses); | ||
fn('ArmyRose', numberDataClasses).then(function (result) { | ||
assert.equal(result.get().length, numberDataClasses); | ||
done(); | ||
@@ -39,0 +39,0 @@ }); |
'use strict'; | ||
var assert = require('assert'); | ||
var fnColorbrewer = require('../../src/fn/fn-colorbrew'); | ||
var fnColorbrewer = require('../../src/fn/fn-colorbrewer'); | ||
@@ -11,3 +11,3 @@ describe('fn-colorbrewer', function () { | ||
fn('Reds').then(function (result) { | ||
assert.equal(result.length, 5); | ||
assert.equal(result.get().length, 5); | ||
done(); | ||
@@ -19,3 +19,3 @@ }); | ||
fn('Reds', 1).then(function (result) { | ||
assert.equal(result.length, 3); | ||
assert.equal(result.get().length, 3); | ||
done(); | ||
@@ -25,5 +25,5 @@ }); | ||
it('should return at most 7 data classes', function (done) { | ||
fn('Reds', 9).then(function (result) { | ||
assert.equal(result.length, 7); | ||
it('should return at most 8 data classes', function (done) { | ||
fn('Dark2', 9).then(function (result) { | ||
assert.equal(result.get().length, 8); | ||
done(); | ||
@@ -36,3 +36,3 @@ }); | ||
fn('Reds', numberDataClasses).then(function (result) { | ||
assert.equal(result.length, numberDataClasses); | ||
assert.equal(result.get().length, numberDataClasses); | ||
done(); | ||
@@ -39,0 +39,0 @@ }); |
@@ -16,2 +16,6 @@ 'use strict'; | ||
}); | ||
it('should work with array range as first argument', function () { | ||
assert.deepEqual(interpolate([0, 10], 3), [0, 5, 10]); | ||
}); | ||
}); |
@@ -19,4 +19,3 @@ #!/usr/bin/env node | ||
var postcss = require('postcss'); | ||
var PostcssTurboCarto = require('../src/postcss-turbo-carto'); | ||
var turbocarto = require('../src/index'); | ||
var SqlApiDatasource = require('../examples/sql-api-datasource'); | ||
@@ -31,3 +30,6 @@ | ||
getRamp: function (column, buckets, method, callback) { | ||
return callback(null, [100000, 250000, 500000, 1e6, 1.5e6, 2e6, 1e7]); | ||
if (method === 'category') { | ||
return callback(null, ['Private Room', 'Entire House', 'Other', 'Complete floor'].slice(0, buckets)); | ||
} | ||
return callback(null, [100000, 250000, 500000, 1e6, 1.5e6, 2e6, 1e7].slice(0, buckets)); | ||
} | ||
@@ -44,10 +46,8 @@ }; | ||
var postCssTurboCarto = new PostcssTurboCarto(datasource); | ||
postcss([postCssTurboCarto.getPlugin()]) | ||
.process(cartocss) | ||
.then(function (result) { | ||
console.log(result.css); | ||
}) | ||
.catch(function (err) { | ||
turbocarto(cartocss, datasource, function (err, result) { | ||
if (err) { | ||
console.error(err); | ||
}); | ||
process.exit(1); | ||
} | ||
console.log(result); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
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
92283
83
2338
213