goog.provide('M.style.Proportional');
goog.require('M.Style');
goog.require('M.style.Point');
/**
* @namespace M.style.Proportional
*/
(function() {
/**
* @classdesc
* Main constructor of the class. Creates a style Proportional
* with parameters specified by the user
*
* @constructor
* @extends {M.Style}
* @param {String}
* @param{number}
* @param{number}
* @param {M.style.Point}
* @param {object}
* @api stable
*/
M.style.Proportional = (function(attributeName, minRadius, maxRadius, style, proportionalFunction, options = {}) {
if (M.utils.isNullOrEmpty(attributeName)) {
M.exception("No se ha especificado el nombre del atributo.");
}
/**
* TODO
* @public
* @type {String}
* @api stable
* @expose
*/
this.attributeName_ = attributeName;
/**
* The minimum radius of the proportionality
* @private
* @type {number}
* @api stable
* @expose
*/
this.minRadius_ = parseInt(minRadius) || 5;
/**
* The maximum radius of the proportionality
* @private
* @type {number}
* @api stable
* @expose
*/
this.maxRadius_ = parseInt(maxRadius) || 15;
/**
* The style point define by user
* @private
* @type {M.Style}
* @api stable
* @expose
*/
this.style_ = style;
/**
* the proportionality function
* @private
* @type {function}
* @api stable
* @expose
*/
this.proportionalFunction_ = proportionalFunction || ((value, minValue, maxValue, minRadius, maxRadius) =>
(((value - minValue) * (maxRadius - minRadius)) / (maxValue - minValue)) + minRadius);
/**
* @public
* @type {Array<M.Feature>}
* @api stable
* @expose
*/
this.layerFeatures_ = [];
if (this.maxRadius_ < this.minRadius_) {
this.minRadius_ = maxRadius;
this.maxRadius_ = minRadius;
}
goog.base(this, options, {});
});
goog.inherits(M.style.Proportional, M.Style);
/**
* This function apply the style to specified layer
* @function
* @public
* @param {M.Layer.Vector} layer - Layer where to apply choropleth style
* @api stable
*/
M.style.Proportional.prototype.apply = function(layer) {
this.layer_ = layer;
this.update_();
};
/**
* This function returns the attribute name defined by user
* @function
* @public
* @return {String} attribute name of Style
* @api stable
*/
M.style.Proportional.prototype.getAttributeName = function() {
return this.attributeName_;
};
/**
* This function set the attribute name defined by user
* @function
* @public
* @param {String} attributeName - attribute name to set
* @api stable
*/
M.style.Proportional.prototype.setAttributeName = function(attributeName) {
this.attributeName_ = attributeName;
this.update_();
return this;
};
/**
* This function returns the style point defined by user
* @function
* @public
* @return {M.style.Point} style point of each feature
*/
M.style.Proportional.prototype.getStyle = function() {
return this.style_;
};
/**
* This function set the style point defined by user
* @function
* @public
* @param {M.style.Point} style - style point to set
* @api stable
*/
M.style.Proportional.prototype.setStyle = function(style) {
this.style_ = style;
this.update_();
return this;
};
/**
* This function get the minimum radius of the style point
* @function
* @public
* @return {number} minimum radius of style point
* @api stable
*/
M.style.Proportional.prototype.getMinRadius = function() {
return this.minRadius_;
};
/**
* This function set proportional function
* @function
* @public
* @param {function} proportionalFunction - proportional function
* @api stable
*/
M.style.Proportional.prototype.setProportionalFunction = function(proportionalFunction) {
this.proportionalFunction_ = proportionalFunction;
this.update_();
};
/**
* This function get proportional function
* @function
* @public
* @return {number} minimum radius of style point
* @api stable
*/
M.style.Proportional.prototype.getProportionalFunction = function() {
return this.proportionalFunction_;
};
/**
* This function set the minimum radius of the style point
* @function
* @public
* @param {number} minRadius - minimum radius of style point
* @api stable
*/
M.style.Proportional.prototype.setMinRadius = function(minRadius) {
this.minRadius_ = parseInt(minRadius);
if (minRadius >= this.maxRadius_) {
// this.maxRadius_ = minRadius + 10;
M.exception("No puede establecerse un radio mínimo mayor que el máximo.");
}
this.update_();
return this;
};
/**
* This function get the maximum radius of the style point
* @function
* @public
* @return {number} maximum radius of style point
* @api stable
*/
M.style.Proportional.prototype.getMaxRadius = function() {
return this.maxRadius_;
};
/**
* This function set the maximum radius of the style point
* @function
* @public
* @param {number} minRadius - maximum radius of style point
* @api stable
*/
M.style.Proportional.prototype.setMaxRadius = function(maxRadius) {
this.maxRadius_ = parseInt(maxRadius);
if (maxRadius <= this.minRadius_) {
// this.minRadius_ = maxRadius - 10;
M.exception("No puede establecerse un radio máximo menor que el mínimo.");
}
this.update_();
return this;
};
/**
* This function updates the canvas of style
*
* @function
* @public
* @api stable
*/
M.style.Proportional.prototype.updateCanvas = function() {
this.updateCanvasPromise_ = new Promise((success, fail) => {
if (!M.utils.isNullOrEmpty(this.layer_)) {
let style = !M.utils.isNullOrEmpty(this.style_) ? this.style_ : this.layer_.getStyle();
if (style instanceof M.style.Simple) {
let featureStyle = style.clone();
if (!(featureStyle instanceof M.style.Point)) {
featureStyle = new M.style.Point(featureStyle.options_);
}
let sizeAttribute = M.style.Proportional.getSizeAttribute_(featureStyle);
let styleMax = featureStyle.clone();
let styleMin = featureStyle.clone();
let maxRadius = this.getMaxRadius();
let minRadius = this.getMinRadius();
styleMax.set(sizeAttribute, maxRadius);
styleMin.set(sizeAttribute, minRadius);
this.loadCanvasImage_(maxRadius, styleMax.toImage(), (canvasImageMax) => {
this.loadCanvasImage_(minRadius, styleMin.toImage(), (canvasImageMin) => {
this.drawGeometryToCanvas(canvasImageMax, canvasImageMin, success);
});
});
}
else if (!M.utils.isNullOrEmpty(style)) {
this.canvas_ = style.canvas_;
success();
}
}
});
};
/**
* TODO
*
* @function
* @public
* @param {CanvasRenderingContext2D} vectorContext - context of style canvas
* @api stable
*/
M.style.Proportional.prototype.loadCanvasImage_ = function(value, url, callbackFn) {
let image = new Image();
image.crossOrigin = 'Anonymous';
image.onload = function() {
callbackFn({
'image': this,
'value': value
});
};
image.onerror = function() {
callbackFn({
'value': value
});
};
image.src = url;
};
/**
* TODO
*
* @function
* @public
* @param {CanvasRenderingContext2D} vectorContext - context of style canvas
* @api stable
*/
M.style.Proportional.prototype.drawGeometryToCanvas = function(canvasImageMax, canvasImageMin, callbackFn) {
let maxImage = canvasImageMax['image'];
let minImage = canvasImageMin['image'];
this.canvas_.height = maxImage.height + 5 + minImage.height + 5;
let vectorContext = this.canvas_.getContext('2d');
vectorContext.textBaseline = "middle";
// MAX VALUE
let coordXText = 0;
let coordYText = 0;
if (!M.utils.isNullOrEmpty(maxImage)) {
coordXText = maxImage.width + 5;
coordYText = maxImage.height / 2;
if (/^https?\:\/\//i.test(maxImage.src)) {
this.canvas_.height = 80 + 40 + 10;
vectorContext.fillText(` max: ${this.maxValue_}`, 85, 40);
vectorContext.drawImage(maxImage, 0, 0, 80, 80);
}
else {
vectorContext.fillText(` max: ${this.maxValue_}`, coordXText, coordYText);
vectorContext.drawImage(maxImage, 0, 0);
}
}
// MIN VALUE
if (!M.utils.isNullOrEmpty(minImage)) {
let coordinateX = 0;
if (!M.utils.isNullOrEmpty(maxImage)) {
coordinateX = (maxImage.width / 2) - (minImage.width / 2);
}
let coordinateY = maxImage.height + 5;
coordYText = coordinateY + (minImage.height / 2);
if (/^https?\:\/\//i.test(minImage.src)) {
vectorContext.fillText(` min: ${this.minValue_}`, 85, 105);
vectorContext.drawImage(minImage, 20, 85, 40, 40);
}
else {
vectorContext.fillText(` min: ${this.minValue_}`, coordXText, coordYText);
vectorContext.drawImage(minImage, coordinateX, coordinateY);
}
}
callbackFn();
};
/**
* This function updates the style
* @function
* @private
* @api stable
*/
M.style.Proportional.prototype.update_ = function() {
if (!M.utils.isNullOrEmpty(this.layer_)) {
let features = this.layer_.getFeatures();
let [minRadius, maxRadius] = [this.minRadius_, this.maxRadius_];
[this.minValue_, this.maxValue_] = M.style.Proportional.getMinMaxValues_(features, this.attributeName_);
features.forEach(function(feature) {
let style;
if (!M.utils.isNullOrEmpty(this.style_)) {
style = this.style_.clone();
}
else {
let featureStyle = feature.getStyle();
if (!M.utils.isNullOrEmpty(featureStyle)) {
style = featureStyle.clone();
}
else {
style = this.layer_.getStyle().clone();
}
}
let featureStyle = style;
if (!(featureStyle instanceof M.style.Point)) {
featureStyle = new M.style.Point(featureStyle.options_);
}
style = this.calculateStyle_(feature, {
minRadius: minRadius,
maxRadius: maxRadius,
minValue: this.minValue_,
maxValue: this.maxValue_,
}, featureStyle);
feature.setStyle(style);
}, this);
this.updateCanvas();
}
};
/**
* This function gets the min value of feature's atributte.
* @function
* @private
* @param {Array<M.Feature>} features - array of features
* @param {String} attributeName - attributeName of style
* @api stable
*/
M.style.Proportional.getMinMaxValues_ = function(features, attributeName) {
let [minValue, maxValue] = [undefined, undefined];
let filteredFeatures = features.filter(feature =>
![NaN, undefined, null].includes(feature.getAttribute(attributeName))).map(f => parseInt(f.getAttribute(attributeName)));
let index = 1;
if (!M.utils.isNullOrEmpty(filteredFeatures)) {
minValue = filteredFeatures[0];
maxValue = filteredFeatures[0];
while (index < filteredFeatures.length - 1) {
let posteriorValue = filteredFeatures[index + 1];
minValue = (minValue < posteriorValue) ? minValue : posteriorValue;
maxValue = (maxValue < posteriorValue) ? posteriorValue : maxValue;
index++;
}
}
return [minValue, maxValue];
};
/**
* This function returns the attribute of style point that controls the size
* @function
* @private
* @return {string} the attribute that controls the size
* @api stable
*/
M.style.Proportional.getSizeAttribute_ = function(style) {
let sizeAttribute = 'radius';
if (!M.utils.isNullOrEmpty(style.get('icon'))) {
if (!M.utils.isNullOrEmpty(style.get('icon.src'))) {
sizeAttribute = 'icon.scale';
}
else {
sizeAttribute = 'icon.radius';
}
}
return sizeAttribute;
};
/**
* This function returns the proportional style of feature
* @function
* @private
* @param {M.Feature} feature
* @param {object} options - minRadius, maxRadius, minValue, maxValue
* @param {M.style.Point} style
* @return {M.style.Simple} the proportional style of feature
* @api stable
*/
M.style.Proportional.prototype.calculateStyle_ = function(feature, options, style) {
if (!M.utils.isNullOrEmpty(style)) {
let [minRadius, maxRadius] = [options.minRadius, options.maxRadius];
if (!M.utils.isNullOrEmpty(style.get('icon.src'))) {
minRadius = options.minRadius / M.style.Proportional.SCALE_PROPORTION;
maxRadius = options.maxRadius / M.style.Proportional.SCALE_PROPORTION;
}
let value = feature.getAttribute(this.attributeName_);
if (value == null) {
console.warn(`Warning: ${this.attributeName_} value is null or empty.`);
}
let radius = this.proportionalFunction_(value, options.minValue, options.maxValue,
minRadius, maxRadius);
let zindex = options.maxValue - parseFloat(feature.getAttribute(this.attributeName_));
style.set(M.style.Proportional.getSizeAttribute_(style), radius);
style.set('zindex', zindex);
}
return style;
};
/**
* This constant defines the scale proportion for iconstyle in styleproportional.
* @constant
* @public
* @api stable
*/
M.style.Proportional.SCALE_PROPORTION = 20;
})();