Initial commit
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
var cdb = require('internal-carto.js');
|
||||
var StyleUtils = require('./style-utils');
|
||||
var CategoryColors = require('./category-colors');
|
||||
var getValue = require('../../util/get-object-value');
|
||||
|
||||
var AutoStyler = cdb.core.Model.extend({
|
||||
initialize: function (dataviewModel, layerModel, options) {
|
||||
this.options = options || {};
|
||||
this.styles = options && options.auto_style;
|
||||
this.dataviewModel = dataviewModel;
|
||||
this.colors = new CategoryColors(this.styles);
|
||||
this.layer = layerModel;
|
||||
},
|
||||
|
||||
getStyle: function () {
|
||||
var style = this.layer.get('initialStyle');
|
||||
if (!style) return;
|
||||
|
||||
AutoStyler.FILL_SELECTORS.forEach(function (item) {
|
||||
style = StyleUtils.changeStyle(style, item, this._getFillColor(item));
|
||||
}.bind(this));
|
||||
|
||||
AutoStyler.OPACITY_SELECTORS.forEach(function (item) {
|
||||
style = StyleUtils.changeStyle(style, item, this._getOpacity());
|
||||
}.bind(this));
|
||||
|
||||
return StyleUtils.replaceWrongSpaceChar(style);
|
||||
},
|
||||
|
||||
_getColor: function () {
|
||||
return getValue(this.styles, 'definition.color');
|
||||
},
|
||||
|
||||
_getOpacity: function () {
|
||||
return getValue(this.styles, 'definition.color.opacity');
|
||||
}
|
||||
});
|
||||
|
||||
AutoStyler.FILL_SELECTORS = ['marker-fill', 'polygon-fill', 'line-color'];
|
||||
AutoStyler.OPACITY_SELECTORS = ['marker-fill-opacity', 'polygon-opacity', 'line-opacity'];
|
||||
|
||||
module.exports = AutoStyler;
|
||||
@@ -0,0 +1,79 @@
|
||||
var _ = require('underscore');
|
||||
var colorScales = [
|
||||
'#7F3C8D',
|
||||
'#11A579',
|
||||
'#3969AC',
|
||||
'#F2B701',
|
||||
'#E73F74'
|
||||
]; // Demo colors
|
||||
|
||||
function getColorRange (styles) {
|
||||
var colorRange = styles && styles.definition &&
|
||||
styles.definition.color &&
|
||||
styles.definition.color.range;
|
||||
|
||||
return colorRange || colorScales;
|
||||
}
|
||||
|
||||
/**
|
||||
* Class to set categories to each color
|
||||
* - Right now, there is a relation 1 color to 1 category.
|
||||
* - If that category is not available in the new data, that
|
||||
* color will be freed.
|
||||
*
|
||||
*/
|
||||
|
||||
function CategoryColors (styles) {
|
||||
this.updateColors(styles);
|
||||
}
|
||||
|
||||
CategoryColors.prototype.updateColors = function (styles) {
|
||||
var colorRange = getColorRange(styles);
|
||||
this.colors = {};
|
||||
_.each(colorRange, function (c) {
|
||||
this.colors[c] = null;
|
||||
}, this);
|
||||
};
|
||||
|
||||
CategoryColors.prototype.updateData = function (d) {
|
||||
// Remove categories from colors where they are not present anymore
|
||||
_.each(this.colors, function (value, key) {
|
||||
if (!_.contains(d, value)) {
|
||||
this.colors[key] = null;
|
||||
} else {
|
||||
d = _.without(d, value);
|
||||
}
|
||||
}, this);
|
||||
|
||||
// Set colors by new categories
|
||||
_.each(d, function (category) {
|
||||
var nextFreeColor = this.getNextAvailableColor();
|
||||
if (nextFreeColor) {
|
||||
this.colors[nextFreeColor] = category;
|
||||
}
|
||||
}, this);
|
||||
};
|
||||
|
||||
CategoryColors.prototype.getNextAvailableColor = function () {
|
||||
for (var i in this.colors) {
|
||||
if (this.colors[i] === null) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
CategoryColors.prototype.getColorByCategory = function (category) {
|
||||
for (var i in this.colors) {
|
||||
if (this.colors[i] === category) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return '#A5AA99';
|
||||
};
|
||||
|
||||
CategoryColors.prototype.getCategoryByColor = function (color) {
|
||||
return this.colors[color];
|
||||
};
|
||||
|
||||
module.exports = CategoryColors;
|
||||
@@ -0,0 +1,67 @@
|
||||
var _ = require('underscore');
|
||||
var AutoStyler = require('./auto-styler');
|
||||
|
||||
module.exports = AutoStyler.extend({
|
||||
updateStyle: function (style) {
|
||||
this.styles = style.auto_style;
|
||||
this.colors.updateColors(style.auto_style);
|
||||
this.colors.updateData(_.pluck(this.dataviewModel.get('data'), 'name'));
|
||||
},
|
||||
|
||||
_getRange: function () {
|
||||
return _.map(this.dataviewModel.get('data'), function (category) {
|
||||
return this.colors.getColorByCategory(category.name);
|
||||
}, this);
|
||||
},
|
||||
|
||||
getDef: function () {
|
||||
var model = this.dataviewModel;
|
||||
var categories = model.get('data');
|
||||
var range = this._getRange();
|
||||
var definitions = {};
|
||||
|
||||
AutoStyler.FILL_SELECTORS.forEach(function (item) {
|
||||
var definition = {};
|
||||
var geom = item.substring(0, item.indexOf('-'));
|
||||
definition = { color:
|
||||
{ domain: _.pluck(categories, 'name'), range: range, attribute: model.get('column') }
|
||||
};
|
||||
definitions[geom === 'marker' ? 'point' : geom] = definition;
|
||||
});
|
||||
|
||||
return definitions;
|
||||
},
|
||||
|
||||
_getFillColor: function () {
|
||||
var model = this.dataviewModel;
|
||||
var categories = model.get('data');
|
||||
var column = model.get('column');
|
||||
|
||||
return this._getCategoryRamp(categories, column);
|
||||
},
|
||||
|
||||
_getCategoryRamp: function (categories, column) {
|
||||
var ramp = 'ramp([' + column + '], ';
|
||||
|
||||
var catListColors = '';
|
||||
var catListValues = '';
|
||||
|
||||
for (var i = 0; i < categories.length; i++) {
|
||||
var cat = categories[i];
|
||||
var next = i !== categories.length - 1 ? ', ' : '';
|
||||
|
||||
catListColors += '"' + this.colors.getColorByCategory(cat.name) + '"' + next;
|
||||
if (!cat.agg) {
|
||||
if (typeof cat.name !== 'string') {
|
||||
catListValues += cat.name + next;
|
||||
} else {
|
||||
catListValues += '"' + String(cat.name).replace(/"/g, '\\"') + '"' + next;
|
||||
}
|
||||
} else if (i === categories.length - 1) {
|
||||
catListValues = catListValues.substring(0, catListValues.length - 2);
|
||||
}
|
||||
}
|
||||
|
||||
return ramp + '(' + catListColors + '), (' + catListValues + '), \'=\')';
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
var Stylers = {
|
||||
category: require('./category'),
|
||||
histogram: require('./histogram')
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
get: function (dataviewModel, layerModel, style) {
|
||||
var AutoStyler = Stylers[dataviewModel.get('type')];
|
||||
if (AutoStyler) {
|
||||
return new AutoStyler(dataviewModel, layerModel, style);
|
||||
} else {
|
||||
throw new Error('dataview type not supported');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,152 @@
|
||||
var _ = require('underscore');
|
||||
var cartocolor = require('cartocolor');
|
||||
var AutoStyler = require('./auto-styler');
|
||||
var StyleUtils = require('./style-utils');
|
||||
|
||||
var HistogramAutoStyler = AutoStyler.extend({
|
||||
updateStyle: function (style) {
|
||||
this.styles = style.auto_style;
|
||||
},
|
||||
|
||||
_getFillColor: function (sym) {
|
||||
var custom = this._getColor();
|
||||
var scales = custom || {};
|
||||
|
||||
if (!custom) {
|
||||
var shape = this.dataviewModel.getDistributionType(
|
||||
this.dataviewModel.getUnfilteredDataModel().get('data')
|
||||
);
|
||||
|
||||
scales = HistogramAutoStyler.SCALES_MAP[sym][shape];
|
||||
}
|
||||
|
||||
var ramp = 'ramp([' + this.dataviewModel.get('column') + '], ';
|
||||
var colors = custom
|
||||
? "('" + scales.range.join("', '") + "'), "
|
||||
: 'cartocolor(' + scales.palette + ', ' + this.dataviewModel.get('bins') + '), ';
|
||||
var cuantification = scales.quantification + ')';
|
||||
|
||||
return ramp + colors + cuantification;
|
||||
},
|
||||
|
||||
getDef: function (cartocss) {
|
||||
var definitions = {};
|
||||
var shape = this.dataviewModel.getDistributionType(
|
||||
this.dataviewModel.getUnfilteredDataModel().get('data')
|
||||
);
|
||||
var bins = this.dataviewModel.get('bins');
|
||||
var attr = this.dataviewModel.get('column');
|
||||
var styles = this.styles;
|
||||
var isCustomDefinition = (this.styles && this.styles.custom) || false;
|
||||
|
||||
AutoStyler.FILL_SELECTORS.forEach(function (item) {
|
||||
if (StyleUtils.isPropertyIncluded(cartocss, item)) {
|
||||
var scales = HistogramAutoStyler.SCALES_MAP[item][shape];
|
||||
var geom = item.substring(0, item.indexOf('-'));
|
||||
var definition = {};
|
||||
|
||||
if (scales) {
|
||||
if (isCustomDefinition === true) {
|
||||
definition = _.extend(definition, styles.definition);
|
||||
} else {
|
||||
definition = {
|
||||
color: {
|
||||
range: cartocolor[scales.palette][bins] || cartocolor[scales.palette][Object.keys(cartocolor[scales.palette]).length],
|
||||
quantification: scales.quantification,
|
||||
attribute: attr
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
definitions[geom === 'marker' ? 'point' : geom] = definition;
|
||||
}
|
||||
});
|
||||
|
||||
return definitions;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
HistogramAutoStyler.SCALES_MAP = {
|
||||
'polygon-fill': {
|
||||
'F': {
|
||||
palette: 'PinkYl',
|
||||
quantification: 'equal'
|
||||
},
|
||||
'L': {
|
||||
palette: 'Emrld',
|
||||
quantification: 'headtails'
|
||||
},
|
||||
'J': {
|
||||
palette: 'Emrld',
|
||||
quantification: 'headtails'
|
||||
},
|
||||
'A': {
|
||||
palette: 'Geyser',
|
||||
quantification: 'quantiles'
|
||||
},
|
||||
'C': {
|
||||
palette: 'Sunset',
|
||||
quantification: 'jenks'
|
||||
},
|
||||
'U': {
|
||||
palette: 'Sunset',
|
||||
quantification: 'jenks'
|
||||
}
|
||||
},
|
||||
'line-color': {
|
||||
'F': {
|
||||
palette: 'PinkYl',
|
||||
quantification: 'equal'
|
||||
},
|
||||
'L': {
|
||||
palette: 'Emrld',
|
||||
quantification: 'headtails'
|
||||
},
|
||||
'J': {
|
||||
palette: 'Emrld',
|
||||
quantification: 'headtails'
|
||||
},
|
||||
'A': {
|
||||
palette: 'Geyser',
|
||||
quantification: 'quantiles'
|
||||
},
|
||||
'C': {
|
||||
palette: 'Sunset',
|
||||
quantification: 'jenks'
|
||||
},
|
||||
'U': {
|
||||
palette: 'Sunset',
|
||||
quantification: 'jenks'
|
||||
}
|
||||
},
|
||||
'marker-fill': {
|
||||
'F': {
|
||||
palette: 'RedOr',
|
||||
quantification: 'equal'
|
||||
},
|
||||
'L': {
|
||||
palette: 'BluYl',
|
||||
quantification: 'headtails'
|
||||
},
|
||||
'J': {
|
||||
palette: 'BluYl',
|
||||
quantification: 'headtails'
|
||||
},
|
||||
'A': {
|
||||
palette: 'Geyser',
|
||||
quantification: 'quantiles'
|
||||
},
|
||||
'C': {
|
||||
palette: 'SunsetDark',
|
||||
quantification: 'jenks'
|
||||
},
|
||||
'U': {
|
||||
palette: 'SunsetDark',
|
||||
quantification: 'jenks'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = HistogramAutoStyler;
|
||||
@@ -0,0 +1,91 @@
|
||||
var _ = require('underscore');
|
||||
var postcss = require('postcss');
|
||||
var stripInlineComments = require('postcss-strip-inline-comments');
|
||||
var SCSSsyntax = require('postcss-scss');
|
||||
|
||||
var OUTLINE_ATTRS = ['line-color', 'line-opacity'];
|
||||
|
||||
function generateCSSTreeFromCartoCSS (cartocss) {
|
||||
return postcss()
|
||||
.use(stripInlineComments)
|
||||
.process(cartocss, { syntax: SCSSsyntax });
|
||||
}
|
||||
|
||||
function isPropertyIncluded (cartocss, attr) {
|
||||
var cssTree = generateCSSTreeFromCartoCSS(cartocss);
|
||||
var root = cssTree.result.root;
|
||||
var propertyIncluded = false;
|
||||
|
||||
if (root) {
|
||||
root.walkDecls(attr, function (node) {
|
||||
var parentNode = node.parent;
|
||||
|
||||
if (!isSelectorRule(parentNode) || isMapnikGeometrySelectorRule(parentNode)) {
|
||||
propertyIncluded = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return propertyIncluded;
|
||||
}
|
||||
|
||||
function isSelectorRule (node) {
|
||||
return node.type === 'rule' && node.selector.search(/\[(.)+\]/g) !== -1;
|
||||
}
|
||||
|
||||
function isMapnikGeometrySelectorRule (node) {
|
||||
return isSelectorRule(node) && node.selector.search('mapnik::geometry_type') !== -1;
|
||||
}
|
||||
|
||||
function isOutlineRule (node) {
|
||||
return node.type === 'rule' && node.selector.search('::outline') !== -1;
|
||||
}
|
||||
|
||||
function replaceWrongSpaceChar (cartocss) {
|
||||
return cartocss.replace(new RegExp(String.fromCharCode(160), 'g'), ' ');
|
||||
}
|
||||
|
||||
/**
|
||||
* Change attr style and remove all the duplicates
|
||||
* @param {String} cartocss cartocss original String
|
||||
* @param {String} attr CSS Attribute ex, polygon-fill
|
||||
* @param {String} newStyle New style value ex, red;
|
||||
* @return {String} Cartocss modified String
|
||||
*/
|
||||
function changeStyle (cartocss, attr, newStyle) {
|
||||
if (_.isUndefined(newStyle)) return cartocss;
|
||||
|
||||
var cssTree = generateCSSTreeFromCartoCSS(cartocss);
|
||||
var root = cssTree.result.root;
|
||||
var attributeAlreadyChanged = false;
|
||||
|
||||
if (root) {
|
||||
root.walkDecls(attr, function (node) {
|
||||
var parentNode = node.parent;
|
||||
|
||||
if (!(isOutlineRule(parentNode) && _.contains(OUTLINE_ATTRS, attr))) {
|
||||
if (isSelectorRule(parentNode) || attributeAlreadyChanged) {
|
||||
// If the attribute is inside a conditional selection, it has to be removed
|
||||
node.remove();
|
||||
} else {
|
||||
// If the attribute is inside a regular root (or symbolizer), it just
|
||||
// changes the value
|
||||
node.value = newStyle;
|
||||
attributeAlreadyChanged = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return cssTree.css;
|
||||
}
|
||||
|
||||
return cartocss;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
changeStyle: _.memoize(changeStyle, function (css, attr, style) {
|
||||
return css + attr + style;
|
||||
}),
|
||||
isPropertyIncluded: isPropertyIncluded,
|
||||
replaceWrongSpaceChar: replaceWrongSpaceChar
|
||||
};
|
||||
Reference in New Issue
Block a user