You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							569 lines
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
	
	
							569 lines
						
					
					
						
							13 KiB
						
					
					
				
 | 
						|
/* | 
						|
* 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 Model = require("../model/Model"); | 
						|
 | 
						|
var linkList = require("./helper/linkList"); | 
						|
 | 
						|
var List = require("./List"); | 
						|
 | 
						|
var createDimensions = require("./helper/createDimensions"); | 
						|
 | 
						|
/* | 
						|
* 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. | 
						|
*/ | 
						|
 | 
						|
/** | 
						|
 * Tree data structure | 
						|
 * | 
						|
 * @module echarts/data/Tree | 
						|
 */ | 
						|
 | 
						|
/** | 
						|
 * @constructor module:echarts/data/Tree~TreeNode | 
						|
 * @param {string} name | 
						|
 * @param {module:echarts/data/Tree} hostTree | 
						|
 */ | 
						|
var TreeNode = function (name, hostTree) { | 
						|
  /** | 
						|
   * @type {string} | 
						|
   */ | 
						|
  this.name = name || ''; | 
						|
  /** | 
						|
   * Depth of node | 
						|
   * | 
						|
   * @type {number} | 
						|
   * @readOnly | 
						|
   */ | 
						|
 | 
						|
  this.depth = 0; | 
						|
  /** | 
						|
   * Height of the subtree rooted at this node. | 
						|
   * @type {number} | 
						|
   * @readOnly | 
						|
   */ | 
						|
 | 
						|
  this.height = 0; | 
						|
  /** | 
						|
   * @type {module:echarts/data/Tree~TreeNode} | 
						|
   * @readOnly | 
						|
   */ | 
						|
 | 
						|
  this.parentNode = null; | 
						|
  /** | 
						|
   * Reference to list item. | 
						|
   * Do not persistent dataIndex outside, | 
						|
   * besause it may be changed by list. | 
						|
   * If dataIndex -1, | 
						|
   * this node is logical deleted (filtered) in list. | 
						|
   * | 
						|
   * @type {Object} | 
						|
   * @readOnly | 
						|
   */ | 
						|
 | 
						|
  this.dataIndex = -1; | 
						|
  /** | 
						|
   * @type {Array.<module:echarts/data/Tree~TreeNode>} | 
						|
   * @readOnly | 
						|
   */ | 
						|
 | 
						|
  this.children = []; | 
						|
  /** | 
						|
   * @type {Array.<module:echarts/data/Tree~TreeNode>} | 
						|
   * @pubilc | 
						|
   */ | 
						|
 | 
						|
  this.viewChildren = []; | 
						|
  /** | 
						|
   * @type {moduel:echarts/data/Tree} | 
						|
   * @readOnly | 
						|
   */ | 
						|
 | 
						|
  this.hostTree = hostTree; | 
						|
}; | 
						|
 | 
						|
TreeNode.prototype = { | 
						|
  constructor: TreeNode, | 
						|
 | 
						|
  /** | 
						|
   * The node is removed. | 
						|
   * @return {boolean} is removed. | 
						|
   */ | 
						|
  isRemoved: function () { | 
						|
    return this.dataIndex < 0; | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * Travel this subtree (include this node). | 
						|
   * Usage: | 
						|
   *    node.eachNode(function () { ... }); // preorder | 
						|
   *    node.eachNode('preorder', function () { ... }); // preorder | 
						|
   *    node.eachNode('postorder', function () { ... }); // postorder | 
						|
   *    node.eachNode( | 
						|
   *        {order: 'postorder', attr: 'viewChildren'}, | 
						|
   *        function () { ... } | 
						|
   *    ); // postorder | 
						|
   * | 
						|
   * @param {(Object|string)} options If string, means order. | 
						|
   * @param {string=} options.order 'preorder' or 'postorder' | 
						|
   * @param {string=} options.attr 'children' or 'viewChildren' | 
						|
   * @param {Function} cb If in preorder and return false, | 
						|
   *                      its subtree will not be visited. | 
						|
   * @param {Object} [context] | 
						|
   */ | 
						|
  eachNode: function (options, cb, context) { | 
						|
    if (typeof options === 'function') { | 
						|
      context = cb; | 
						|
      cb = options; | 
						|
      options = null; | 
						|
    } | 
						|
 | 
						|
    options = options || {}; | 
						|
 | 
						|
    if (zrUtil.isString(options)) { | 
						|
      options = { | 
						|
        order: options | 
						|
      }; | 
						|
    } | 
						|
 | 
						|
    var order = options.order || 'preorder'; | 
						|
    var children = this[options.attr || 'children']; | 
						|
    var suppressVisitSub; | 
						|
    order === 'preorder' && (suppressVisitSub = cb.call(context, this)); | 
						|
 | 
						|
    for (var i = 0; !suppressVisitSub && i < children.length; i++) { | 
						|
      children[i].eachNode(options, cb, context); | 
						|
    } | 
						|
 | 
						|
    order === 'postorder' && cb.call(context, this); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * Update depth and height of this subtree. | 
						|
   * | 
						|
   * @param  {number} depth | 
						|
   */ | 
						|
  updateDepthAndHeight: function (depth) { | 
						|
    var height = 0; | 
						|
    this.depth = depth; | 
						|
 | 
						|
    for (var i = 0; i < this.children.length; i++) { | 
						|
      var child = this.children[i]; | 
						|
      child.updateDepthAndHeight(depth + 1); | 
						|
 | 
						|
      if (child.height > height) { | 
						|
        height = child.height; | 
						|
      } | 
						|
    } | 
						|
 | 
						|
    this.height = height + 1; | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @param  {string} id | 
						|
   * @return {module:echarts/data/Tree~TreeNode} | 
						|
   */ | 
						|
  getNodeById: function (id) { | 
						|
    if (this.getId() === id) { | 
						|
      return this; | 
						|
    } | 
						|
 | 
						|
    for (var i = 0, children = this.children, len = children.length; i < len; i++) { | 
						|
      var res = children[i].getNodeById(id); | 
						|
 | 
						|
      if (res) { | 
						|
        return res; | 
						|
      } | 
						|
    } | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @param {module:echarts/data/Tree~TreeNode} node | 
						|
   * @return {boolean} | 
						|
   */ | 
						|
  contains: function (node) { | 
						|
    if (node === this) { | 
						|
      return true; | 
						|
    } | 
						|
 | 
						|
    for (var i = 0, children = this.children, len = children.length; i < len; i++) { | 
						|
      var res = children[i].contains(node); | 
						|
 | 
						|
      if (res) { | 
						|
        return res; | 
						|
      } | 
						|
    } | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @param {boolean} includeSelf Default false. | 
						|
   * @return {Array.<module:echarts/data/Tree~TreeNode>} order: [root, child, grandchild, ...] | 
						|
   */ | 
						|
  getAncestors: function (includeSelf) { | 
						|
    var ancestors = []; | 
						|
    var node = includeSelf ? this : this.parentNode; | 
						|
 | 
						|
    while (node) { | 
						|
      ancestors.push(node); | 
						|
      node = node.parentNode; | 
						|
    } | 
						|
 | 
						|
    ancestors.reverse(); | 
						|
    return ancestors; | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @param {string|Array=} [dimension='value'] Default 'value'. can be 0, 1, 2, 3 | 
						|
   * @return {number} Value. | 
						|
   */ | 
						|
  getValue: function (dimension) { | 
						|
    var data = this.hostTree.data; | 
						|
    return data.get(data.getDimension(dimension || 'value'), this.dataIndex); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @param {Object} layout | 
						|
   * @param {boolean=} [merge=false] | 
						|
   */ | 
						|
  setLayout: function (layout, merge) { | 
						|
    this.dataIndex >= 0 && this.hostTree.data.setItemLayout(this.dataIndex, layout, merge); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @return {Object} layout | 
						|
   */ | 
						|
  getLayout: function () { | 
						|
    return this.hostTree.data.getItemLayout(this.dataIndex); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @param {string} [path] | 
						|
   * @return {module:echarts/model/Model} | 
						|
   */ | 
						|
  getModel: function (path) { | 
						|
    if (this.dataIndex < 0) { | 
						|
      return; | 
						|
    } | 
						|
 | 
						|
    var hostTree = this.hostTree; | 
						|
    var itemModel = hostTree.data.getItemModel(this.dataIndex); | 
						|
    var levelModel = this.getLevelModel(); | 
						|
    var leavesModel; | 
						|
 | 
						|
    if (!levelModel && (this.children.length === 0 || this.children.length !== 0 && this.isExpand === false)) { | 
						|
      leavesModel = this.getLeavesModel(); | 
						|
    } | 
						|
 | 
						|
    return itemModel.getModel(path, (levelModel || leavesModel || hostTree.hostModel).getModel(path)); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @return {module:echarts/model/Model} | 
						|
   */ | 
						|
  getLevelModel: function () { | 
						|
    return (this.hostTree.levelModels || [])[this.depth]; | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @return {module:echarts/model/Model} | 
						|
   */ | 
						|
  getLeavesModel: function () { | 
						|
    return this.hostTree.leavesModel; | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @example | 
						|
   *  setItemVisual('color', color); | 
						|
   *  setItemVisual({ | 
						|
   *      'color': color | 
						|
   *  }); | 
						|
   */ | 
						|
  setVisual: function (key, value) { | 
						|
    this.dataIndex >= 0 && this.hostTree.data.setItemVisual(this.dataIndex, key, value); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * Get item visual | 
						|
   */ | 
						|
  getVisual: function (key, ignoreParent) { | 
						|
    return this.hostTree.data.getItemVisual(this.dataIndex, key, ignoreParent); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @public | 
						|
   * @return {number} | 
						|
   */ | 
						|
  getRawIndex: function () { | 
						|
    return this.hostTree.data.getRawIndex(this.dataIndex); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @public | 
						|
   * @return {string} | 
						|
   */ | 
						|
  getId: function () { | 
						|
    return this.hostTree.data.getId(this.dataIndex); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * if this is an ancestor of another node | 
						|
   * | 
						|
   * @public | 
						|
   * @param {TreeNode} node another node | 
						|
   * @return {boolean} if is ancestor | 
						|
   */ | 
						|
  isAncestorOf: function (node) { | 
						|
    var parent = node.parentNode; | 
						|
 | 
						|
    while (parent) { | 
						|
      if (parent === this) { | 
						|
        return true; | 
						|
      } | 
						|
 | 
						|
      parent = parent.parentNode; | 
						|
    } | 
						|
 | 
						|
    return false; | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * if this is an descendant of another node | 
						|
   * | 
						|
   * @public | 
						|
   * @param {TreeNode} node another node | 
						|
   * @return {boolean} if is descendant | 
						|
   */ | 
						|
  isDescendantOf: function (node) { | 
						|
    return node !== this && node.isAncestorOf(this); | 
						|
  } | 
						|
}; | 
						|
/** | 
						|
 * @constructor | 
						|
 * @alias module:echarts/data/Tree | 
						|
 * @param {module:echarts/model/Model} hostModel | 
						|
 * @param {Array.<Object>} levelOptions | 
						|
 * @param {Object} leavesOption | 
						|
 */ | 
						|
 | 
						|
function Tree(hostModel, levelOptions, leavesOption) { | 
						|
  /** | 
						|
   * @type {module:echarts/data/Tree~TreeNode} | 
						|
   * @readOnly | 
						|
   */ | 
						|
  this.root; | 
						|
  /** | 
						|
   * @type {module:echarts/data/List} | 
						|
   * @readOnly | 
						|
   */ | 
						|
 | 
						|
  this.data; | 
						|
  /** | 
						|
   * Index of each item is the same as the raw index of coresponding list item. | 
						|
   * @private | 
						|
   * @type {Array.<module:echarts/data/Tree~TreeNode} | 
						|
   */ | 
						|
 | 
						|
  this._nodes = []; | 
						|
  /** | 
						|
   * @private | 
						|
   * @readOnly | 
						|
   * @type {module:echarts/model/Model} | 
						|
   */ | 
						|
 | 
						|
  this.hostModel = hostModel; | 
						|
  /** | 
						|
   * @private | 
						|
   * @readOnly | 
						|
   * @type {Array.<module:echarts/model/Model} | 
						|
   */ | 
						|
 | 
						|
  this.levelModels = zrUtil.map(levelOptions || [], function (levelDefine) { | 
						|
    return new Model(levelDefine, hostModel, hostModel.ecModel); | 
						|
  }); | 
						|
  this.leavesModel = new Model(leavesOption || {}, hostModel, hostModel.ecModel); | 
						|
} | 
						|
 | 
						|
Tree.prototype = { | 
						|
  constructor: Tree, | 
						|
  type: 'tree', | 
						|
 | 
						|
  /** | 
						|
   * Travel this subtree (include this node). | 
						|
   * Usage: | 
						|
   *    node.eachNode(function () { ... }); // preorder | 
						|
   *    node.eachNode('preorder', function () { ... }); // preorder | 
						|
   *    node.eachNode('postorder', function () { ... }); // postorder | 
						|
   *    node.eachNode( | 
						|
   *        {order: 'postorder', attr: 'viewChildren'}, | 
						|
   *        function () { ... } | 
						|
   *    ); // postorder | 
						|
   * | 
						|
   * @param {(Object|string)} options If string, means order. | 
						|
   * @param {string=} options.order 'preorder' or 'postorder' | 
						|
   * @param {string=} options.attr 'children' or 'viewChildren' | 
						|
   * @param {Function} cb | 
						|
   * @param {Object}   [context] | 
						|
   */ | 
						|
  eachNode: function (options, cb, context) { | 
						|
    this.root.eachNode(options, cb, context); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @param {number} dataIndex | 
						|
   * @return {module:echarts/data/Tree~TreeNode} | 
						|
   */ | 
						|
  getNodeByDataIndex: function (dataIndex) { | 
						|
    var rawIndex = this.data.getRawIndex(dataIndex); | 
						|
    return this._nodes[rawIndex]; | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * @param {string} name | 
						|
   * @return {module:echarts/data/Tree~TreeNode} | 
						|
   */ | 
						|
  getNodeByName: function (name) { | 
						|
    return this.root.getNodeByName(name); | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * Update item available by list, | 
						|
   * when list has been performed options like 'filterSelf' or 'map'. | 
						|
   */ | 
						|
  update: function () { | 
						|
    var data = this.data; | 
						|
    var nodes = this._nodes; | 
						|
 | 
						|
    for (var i = 0, len = nodes.length; i < len; i++) { | 
						|
      nodes[i].dataIndex = -1; | 
						|
    } | 
						|
 | 
						|
    for (var i = 0, len = data.count(); i < len; i++) { | 
						|
      nodes[data.getRawIndex(i)].dataIndex = i; | 
						|
    } | 
						|
  }, | 
						|
 | 
						|
  /** | 
						|
   * Clear all layouts | 
						|
   */ | 
						|
  clearLayouts: function () { | 
						|
    this.data.clearItemLayouts(); | 
						|
  } | 
						|
}; | 
						|
/** | 
						|
 * data node format: | 
						|
 * { | 
						|
 *     name: ... | 
						|
 *     value: ... | 
						|
 *     children: [ | 
						|
 *         { | 
						|
 *             name: ... | 
						|
 *             value: ... | 
						|
 *             children: ... | 
						|
 *         }, | 
						|
 *         ... | 
						|
 *     ] | 
						|
 * } | 
						|
 * | 
						|
 * @static | 
						|
 * @param {Object} dataRoot Root node. | 
						|
 * @param {module:echarts/model/Model} hostModel | 
						|
 * @param {Object} treeOptions | 
						|
 * @param {Array.<Object>} treeOptions.levels | 
						|
 * @param {Array.<Object>} treeOptions.leaves | 
						|
 * @return module:echarts/data/Tree | 
						|
 */ | 
						|
 | 
						|
Tree.createTree = function (dataRoot, hostModel, treeOptions) { | 
						|
  var tree = new Tree(hostModel, treeOptions.levels, treeOptions.leaves); | 
						|
  var listData = []; | 
						|
  var dimMax = 1; | 
						|
  buildHierarchy(dataRoot); | 
						|
 | 
						|
  function buildHierarchy(dataNode, parentNode) { | 
						|
    var value = dataNode.value; | 
						|
    dimMax = Math.max(dimMax, zrUtil.isArray(value) ? value.length : 1); | 
						|
    listData.push(dataNode); | 
						|
    var node = new TreeNode(dataNode.name, tree); | 
						|
    parentNode ? addChild(node, parentNode) : tree.root = node; | 
						|
 | 
						|
    tree._nodes.push(node); | 
						|
 | 
						|
    var children = dataNode.children; | 
						|
 | 
						|
    if (children) { | 
						|
      for (var i = 0; i < children.length; i++) { | 
						|
        buildHierarchy(children[i], node); | 
						|
      } | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  tree.root.updateDepthAndHeight(0); | 
						|
  var dimensionsInfo = createDimensions(listData, { | 
						|
    coordDimensions: ['value'], | 
						|
    dimensionsCount: dimMax | 
						|
  }); | 
						|
  var list = new List(dimensionsInfo, hostModel); | 
						|
  list.initData(listData); | 
						|
  linkList({ | 
						|
    mainData: list, | 
						|
    struct: tree, | 
						|
    structAttr: 'tree' | 
						|
  }); | 
						|
  tree.update(); | 
						|
  return tree; | 
						|
}; | 
						|
/** | 
						|
 * It is needed to consider the mess of 'list', 'hostModel' when creating a TreeNote, | 
						|
 * so this function is not ready and not necessary to be public. | 
						|
 * | 
						|
 * @param {(module:echarts/data/Tree~TreeNode|Object)} child | 
						|
 */ | 
						|
 | 
						|
 | 
						|
function addChild(child, node) { | 
						|
  var children = node.children; | 
						|
 | 
						|
  if (child.parentNode === node) { | 
						|
    return; | 
						|
  } | 
						|
 | 
						|
  children.push(child); | 
						|
  child.parentNode = node; | 
						|
} | 
						|
 | 
						|
var _default = Tree; | 
						|
module.exports = _default; |