
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/

var zrUtil = require("zrender/lib/core/util");

var SeriesModel = require("../../model/Series");

var Tree = require("../../data/Tree");

var Model = require("../../model/Model");

var _format = require("../../util/format");

var encodeHTML = _format.encodeHTML;
var addCommas = _format.addCommas;

var _treeHelper = require("../helper/treeHelper");

var wrapTreePathInfo = _treeHelper.wrapTreePathInfo;

/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements.  See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership.  The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License.  You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied.  See the License for the
* specific language governing permissions and limitations
* under the License.
*/
var _default = SeriesModel.extend({
  type: 'series.treemap',
  layoutMode: 'box',
  dependencies: ['grid', 'polar'],
  preventUsingHoverLayer: true,

  /**
   * @type {module:echarts/data/Tree~Node}
   */
  _viewRoot: null,
  defaultOption: {
    // Disable progressive rendering
    progressive: 0,
    // center: ['50%', '50%'],          // not supported in ec3.
    // size: ['80%', '80%'],            // deprecated, compatible with ec2.
    left: 'center',
    top: 'middle',
    right: null,
    bottom: null,
    width: '80%',
    height: '80%',
    sort: true,
    // Can be null or false or true
    // (order by desc default, asc not supported yet (strange effect))
    clipWindow: 'origin',
    // Size of clipped window when zooming. 'origin' or 'fullscreen'
    squareRatio: 0.5 * (1 + Math.sqrt(5)),
    // golden ratio
    leafDepth: null,
    // Nodes on depth from root are regarded as leaves.
    // Count from zero (zero represents only view root).
    drillDownIcon: '▶',
    // Use html character temporarily because it is complicated
    // to align specialized icon. ▷▶❒❐▼✚
    zoomToNodeRatio: 0.32 * 0.32,
    // Be effective when using zoomToNode. Specify the proportion of the
    // target node area in the view area.
    roam: true,
    // true, false, 'scale' or 'zoom', 'move'.
    nodeClick: 'zoomToNode',
    // Leaf node click behaviour: 'zoomToNode', 'link', false.
    // If leafDepth is set and clicking a node which has children but
    // be on left depth, the behaviour would be changing root. Otherwise
    // use behavious defined above.
    animation: true,
    animationDurationUpdate: 900,
    animationEasing: 'quinticInOut',
    breadcrumb: {
      show: true,
      height: 22,
      left: 'center',
      top: 'bottom',
      // right
      // bottom
      emptyItemWidth: 25,
      // Width of empty node.
      itemStyle: {
        color: 'rgba(0,0,0,0.7)',
        //'#5793f3',
        borderColor: 'rgba(255,255,255,0.7)',
        borderWidth: 1,
        shadowColor: 'rgba(150,150,150,1)',
        shadowBlur: 3,
        shadowOffsetX: 0,
        shadowOffsetY: 0,
        textStyle: {
          color: '#fff'
        }
      },
      emphasis: {
        textStyle: {}
      }
    },
    label: {
      show: true,
      // Do not use textDistance, for ellipsis rect just the same as treemap node rect.
      distance: 0,
      padding: 5,
      position: 'inside',
      // Can be [5, '5%'] or position stirng like 'insideTopLeft', ...
      // formatter: null,
      color: '#fff',
      ellipsis: true // align
      // verticalAlign

    },
    upperLabel: {
      // Label when node is parent.
      show: false,
      position: [0, '50%'],
      height: 20,
      // formatter: null,
      color: '#fff',
      ellipsis: true,
      // align: null,
      verticalAlign: 'middle'
    },
    itemStyle: {
      color: null,
      // Can be 'none' if not necessary.
      colorAlpha: null,
      // Can be 'none' if not necessary.
      colorSaturation: null,
      // Can be 'none' if not necessary.
      borderWidth: 0,
      gapWidth: 0,
      borderColor: '#fff',
      borderColorSaturation: null // If specified, borderColor will be ineffective, and the
      // border color is evaluated by color of current node and
      // borderColorSaturation.

    },
    emphasis: {
      upperLabel: {
        show: true,
        position: [0, '50%'],
        color: '#fff',
        ellipsis: true,
        verticalAlign: 'middle'
      }
    },
    visualDimension: 0,
    // Can be 0, 1, 2, 3.
    visualMin: null,
    visualMax: null,
    color: [],
    // + treemapSeries.color should not be modified. Please only modified
    // level[n].color (if necessary).
    // + Specify color list of each level. level[0].color would be global
    // color list if not specified. (see method `setDefault`).
    // + But set as a empty array to forbid fetch color from global palette
    // when using nodeModel.get('color'), otherwise nodes on deep level
    // will always has color palette set and are not able to inherit color
    // from parent node.
    // + TreemapSeries.color can not be set as 'none', otherwise effect
    // legend color fetching (see seriesColor.js).
    colorAlpha: null,
    // Array. Specify color alpha range of each level, like [0.2, 0.8]
    colorSaturation: null,
    // Array. Specify color saturation of each level, like [0.2, 0.5]
    colorMappingBy: 'index',
    // 'value' or 'index' or 'id'.
    visibleMin: 10,
    // If area less than this threshold (unit: pixel^2), node will not
    // be rendered. Only works when sort is 'asc' or 'desc'.
    childrenVisibleMin: null,
    // If area of a node less than this threshold (unit: pixel^2),
    // grandchildren will not show.
    // Why grandchildren? If not grandchildren but children,
    // some siblings show children and some not,
    // the appearance may be mess and not consistent,
    levels: [] // Each item: {
    //     visibleMin, itemStyle, visualDimension, label
    // }
    // data: {
    //      value: [],
    //      children: [],
    //      link: 'http://xxx.xxx.xxx',
    //      target: 'blank' or 'self'
    // }

  },

  /**
   * @override
   */
  getInitialData: function (option, ecModel) {
    // Create a virtual root.
    var root = {
      name: option.name,
      children: option.data
    };
    completeTreeValue(root);
    var levels = option.levels || []; // Used in "visual priority" in `treemapVisual.js`.
    // This way is a little tricky, must satisfy the precondition:
    //   1. There is no `treeNode.getModel('itemStyle.xxx')` used.
    //   2. The `Model.prototype.getModel()` will not use any clone-like way.

    var designatedVisualItemStyle = this.designatedVisualItemStyle = {};
    var designatedVisualModel = new Model({
      itemStyle: designatedVisualItemStyle
    }, this, ecModel);
    levels = option.levels = setDefault(levels, ecModel);
    var levelModels = zrUtil.map(levels || [], function (levelDefine) {
      return new Model(levelDefine, designatedVisualModel, ecModel);
    }, this); // Make sure always a new tree is created when setOption,
    // in TreemapView, we check whether oldTree === newTree
    // to choose mappings approach among old shapes and new shapes.

    var tree = Tree.createTree(root, this, beforeLink);

    function beforeLink(nodeData) {
      nodeData.wrapMethod('getItemModel', function (model, idx) {
        var node = tree.getNodeByDataIndex(idx);
        var levelModel = levelModels[node.depth]; // If no levelModel, we also need `designatedVisualModel`.

        model.parentModel = levelModel || designatedVisualModel;
        return model;
      });
    }

    return tree.data;
  },
  optionUpdated: function () {
    this.resetViewRoot();
  },

  /**
   * @override
   * @param {number} dataIndex
   * @param {boolean} [mutipleSeries=false]
   */
  formatTooltip: function (dataIndex) {
    var data = this.getData();
    var value = this.getRawValue(dataIndex);
    var formattedValue = zrUtil.isArray(value) ? addCommas(value[0]) : addCommas(value);
    var name = data.getName(dataIndex);
    return encodeHTML(name + ': ' + formattedValue);
  },

  /**
   * Add tree path to tooltip param
   *
   * @override
   * @param {number} dataIndex
   * @return {Object}
   */
  getDataParams: function (dataIndex) {
    var params = SeriesModel.prototype.getDataParams.apply(this, arguments);
    var node = this.getData().tree.getNodeByDataIndex(dataIndex);
    params.treePathInfo = wrapTreePathInfo(node, this);
    return params;
  },

  /**
   * @public
   * @param {Object} layoutInfo {
   *                                x: containerGroup x
   *                                y: containerGroup y
   *                                width: containerGroup width
   *                                height: containerGroup height
   *                            }
   */
  setLayoutInfo: function (layoutInfo) {
    /**
     * @readOnly
     * @type {Object}
     */
    this.layoutInfo = this.layoutInfo || {};
    zrUtil.extend(this.layoutInfo, layoutInfo);
  },

  /**
   * @param  {string} id
   * @return {number} index
   */
  mapIdToIndex: function (id) {
    // A feature is implemented:
    // index is monotone increasing with the sequence of
    // input id at the first time.
    // This feature can make sure that each data item and its
    // mapped color have the same index between data list and
    // color list at the beginning, which is useful for user
    // to adjust data-color mapping.

    /**
     * @private
     * @type {Object}
     */
    var idIndexMap = this._idIndexMap;

    if (!idIndexMap) {
      idIndexMap = this._idIndexMap = zrUtil.createHashMap();
      /**
       * @private
       * @type {number}
       */

      this._idIndexMapCount = 0;
    }

    var index = idIndexMap.get(id);

    if (index == null) {
      idIndexMap.set(id, index = this._idIndexMapCount++);
    }

    return index;
  },
  getViewRoot: function () {
    return this._viewRoot;
  },

  /**
   * @param {module:echarts/data/Tree~Node} [viewRoot]
   */
  resetViewRoot: function (viewRoot) {
    viewRoot ? this._viewRoot = viewRoot : viewRoot = this._viewRoot;
    var root = this.getRawData().tree.root;

    if (!viewRoot || viewRoot !== root && !root.contains(viewRoot)) {
      this._viewRoot = root;
    }
  }
});
/**
 * @param {Object} dataNode
 */


function completeTreeValue(dataNode) {
  // Postorder travel tree.
  // If value of none-leaf node is not set,
  // calculate it by suming up the value of all children.
  var sum = 0;
  zrUtil.each(dataNode.children, function (child) {
    completeTreeValue(child);
    var childValue = child.value;
    zrUtil.isArray(childValue) && (childValue = childValue[0]);
    sum += childValue;
  });
  var thisValue = dataNode.value;

  if (zrUtil.isArray(thisValue)) {
    thisValue = thisValue[0];
  }

  if (thisValue == null || isNaN(thisValue)) {
    thisValue = sum;
  } // Value should not less than 0.


  if (thisValue < 0) {
    thisValue = 0;
  }

  zrUtil.isArray(dataNode.value) ? dataNode.value[0] = thisValue : dataNode.value = thisValue;
}
/**
 * set default to level configuration
 */


function setDefault(levels, ecModel) {
  var globalColorList = ecModel.get('color');

  if (!globalColorList) {
    return;
  }

  levels = levels || [];
  var hasColorDefine;
  zrUtil.each(levels, function (levelDefine) {
    var model = new Model(levelDefine);
    var modelColor = model.get('color');

    if (model.get('itemStyle.color') || modelColor && modelColor !== 'none') {
      hasColorDefine = true;
    }
  });

  if (!hasColorDefine) {
    var level0 = levels[0] || (levels[0] = {});
    level0.color = globalColorList.slice();
  }

  return levels;
}

module.exports = _default;