532 lines
12 KiB
JavaScript
532 lines
12 KiB
JavaScript
|
|
/*
|
|
* 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 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);
|
|
return itemModel.getModel(path);
|
|
},
|
|
|
|
/**
|
|
* @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
|
|
*/
|
|
|
|
function Tree(hostModel) {
|
|
/**
|
|
* @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;
|
|
}
|
|
|
|
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
|
|
* @return module:echarts/data/Tree
|
|
*/
|
|
|
|
Tree.createTree = function (dataRoot, hostModel, beforeLink) {
|
|
var tree = new Tree(hostModel);
|
|
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);
|
|
beforeLink && beforeLink(list);
|
|
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; |