Source: style/stylecategory.js

goog.provide('M.style.Category');
goog.require('M.Style');

/**
 * @namespace M.style.Category
 */
(function() {
  /**
   * @classdesc
   * Main constructor of the class. Creates a categoryStyle
   * with parameters specified by the user
   * for the implementation
   * provided by the user
   * @constructor
   * @extends {M.Style}
   * @param {String} attributeName
   * @param {Map<String,M.Style>} categoryStyles
   * @api stable
   */
  M.style.Category = (function(attributeName, categoryStyles, 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;

    /**
     * TODO
     * @public
     * @type {Map<String,M.Style>}
     * @api stable
     * @expose
     */
    this.categoryStyles_ = categoryStyles;
    goog.base(this, options, {});
  });
  goog.inherits(M.style.Category, M.Style);

  /**
   * This function apply the styleCategory object to specified layer
   *
   * @function
   * @public
   * @param {M.layer.Vector} layer - layer is the layer where we want to apply the new Style
   * @returns {M.style.Category}
   * @api stable
   */
  M.style.Category.prototype.apply = function(layer) {
    this.layer_ = layer;
    this.update_();
  };
  /**
   * This function return the AttributeName
   *
   * @function
   * @public
   * @returns {String}
   * @api stable
   */
  M.style.Category.prototype.getAttributeName = function() {
    return this.attributeName_;
  };

  /**
   * This function set the AttributeName defined by user
   *
   * @function
   * @public
   * @param {String} attributeName - newAttributeName is the newAttributeName specified by the user
   * @returns {M.style.Category}
   * @api stable
   */
  M.style.Category.prototype.setAttributeName = function(attributeName) {
    this.attributeName_ = attributeName;
    this.update_();
    return this;
  };

  /**
   * This function return an Array with the diferents Categories
   *
   * @function
   * @public
   * @returns {Array<String>}
   * @api stable
   */
  M.style.Category.prototype.getCategories = function() {
    return this.categoryStyles_;
  };

  /**
   * This function sets the object categories
   *
   * @function
   * @public
   * @param {Map<String,M.style>} categories
   * @return {M.style.styleCategory}
   * @api stable
   *
   */
  M.style.Category.prototype.setCategories = function(categories) {
    this.categoryStyles_ = categories;
    this.update_();
    return this;
  };

  /**
   * This function return the style of a specified Category defined by user
   *
   * @function
   * @public
   * @param {String} string - string is the name of a category value
   * @returns {M.style}
   * @api stable
   */
  M.style.Category.prototype.getStyleForCategory = function(category) {
    return this.categoryStyles_[category];
  };

  /**
   * This function set the style of a specified Category defined by user
   *
   * @function
   * @public
   * @param {String} category - category is the name of a category value
   * @param {M.style.Simple} style - style is the new style to switch
   * @returns {M.style.Category}
   * @api stable
   */
  M.style.Category.prototype.setStyleForCategory = function(category, style) {
    this.categoryStyles_[category] = style;
    this.update_();
    return this;
  };

  /**
   * This function updates the canvas of style
   *
   * @function
   * @public
   * @api stable
   */
  M.style.Category.prototype.updateCanvas = function() {
    let canvasImages = [];
    this.updateCanvasPromise_ = new Promise((success, fail) =>
      this.loadCanvasImages_(0, canvasImages, success));
  };

  /**
   * TODO
   *
   * @function
   * @private
   * @param {CanvasRenderingContext2D} vectorContext - context of style canvas
   */
  M.style.Category.prototype.loadCanvasImages_ = function(currentIndex, canvasImages, callbackFn) {
    let categories = this.getCategories();
    let categoryNames = Object.keys(categories);

    // base case
    if (currentIndex === categoryNames.length) {
      this.drawGeometryToCanvas(canvasImages, callbackFn);
    }
    // recursive case
    else {
      let category = categoryNames[currentIndex];
      let style = this.getStyleForCategory(category);
      let image = new Image();
      image.crossOrigin = 'Anonymous';
      let scope_ = this;
      image.onload = function() {
        canvasImages.push({
          'image': this,
          'categoryName': category
        });
        scope_.loadCanvasImages_((currentIndex + 1), canvasImages, callbackFn);
      };
      image.onerror = function() {
        canvasImages.push({
          'categoryName': category
        });
        scope_.loadCanvasImages_((currentIndex + 1), canvasImages, callbackFn);
      };
      style.updateCanvas();
      if (style.get('icon.src')) {
        M.utils.getImageSize(style.get('icon.src')).then((img) => {
          image.width = style.get('icon.scale') ? img.width * style.get('icon.scale') : img.width;
          image.height = style.get('icon.scale') ? img.height * style.get('icon.scale') : img.height;
          image.src = style.toImage();

        });
      }
      else {
        image.src = style.toImage();
      }
    }
  };

  /**
   * TODO
   *
   * @function
   * @public
   * @param {CanvasRenderingContext2D} vectorContext - context of style canvas
   * @api stable
   */
  M.style.Category.prototype.drawGeometryToCanvas = function(canvasImages, callbackFn) {
    let heights = canvasImages.map(canvasImage => canvasImage['image'].height);
    let widths = canvasImages.map(canvasImage => canvasImage['image'].width);

    let vectorContext = this.canvas_.getContext('2d');
    vectorContext.canvas.height = heights.reduce((acc, h) => acc + h + 5);
    vectorContext.textBaseline = "middle";

    let maxWidth = Math.max.apply(widths, widths);
    canvasImages.forEach((canvasImage, index) => {
      let image = canvasImage['image'];
      let categoryName = canvasImage['categoryName'];
      let coordinateY = 0;
      let prevHeights = heights.slice(0, index);
      if (!M.utils.isNullOrEmpty(prevHeights)) {
        coordinateY = prevHeights.reduce((acc, h) => acc + h + 5);
        coordinateY += 5;
      }
      let imageHeight = 0;
      if (!M.utils.isNullOrEmpty(image)) {
        imageHeight = image.height;
        vectorContext.drawImage(image, (maxWidth - image.width) / 2, coordinateY, image.width, image.height);
      }
      vectorContext.fillText(categoryName, maxWidth + 5, coordinateY + (imageHeight / 2));
    }, this);

    callbackFn();
  };

  /**
   * This function updates the style
   *
   * @function
   * @private
   * @return {M.style.styleCategory}
   * @api stable
   */
  M.style.Category.prototype.update_ = function() {
    if (!M.utils.isNullOrEmpty(this.layer_)) {
      let styleOther = this.categoryStyles_['other'];
      this.layer_.getFeatures().forEach(function(feature) {
        let value = feature.getAttribute(this.attributeName_);
        let style = this.categoryStyles_[value];
        if (!M.utils.isNullOrEmpty(style)) {
          feature.setStyle(style);
        }
        else if (!M.utils.isNullOrEmpty(styleOther)) {
          feature.setStyle(styleOther);
        }
      }.bind(this));
      this.updateCanvas();
    }
  };
})();