632 lines
24 KiB
JavaScript
632 lines
24 KiB
JavaScript
|
define('echarts/chart/treemap', [
|
||
|
'require',
|
||
|
'./base',
|
||
|
'zrender/tool/area',
|
||
|
'zrender/shape/Rectangle',
|
||
|
'zrender/shape/Text',
|
||
|
'zrender/shape/Line',
|
||
|
'../layout/TreeMap',
|
||
|
'../data/Tree',
|
||
|
'../config',
|
||
|
'../util/ecData',
|
||
|
'zrender/config',
|
||
|
'zrender/tool/event',
|
||
|
'zrender/tool/util',
|
||
|
'zrender/tool/color',
|
||
|
'../chart'
|
||
|
], function (require) {
|
||
|
var ChartBase = require('./base');
|
||
|
var toolArea = require('zrender/tool/area');
|
||
|
var RectangleShape = require('zrender/shape/Rectangle');
|
||
|
var TextShape = require('zrender/shape/Text');
|
||
|
var LineShape = require('zrender/shape/Line');
|
||
|
var TreeMapLayout = require('../layout/TreeMap');
|
||
|
var Tree = require('../data/Tree');
|
||
|
var ecConfig = require('../config');
|
||
|
ecConfig.treemap = {
|
||
|
zlevel: 0,
|
||
|
z: 1,
|
||
|
calculable: false,
|
||
|
clickable: true,
|
||
|
center: [
|
||
|
'50%',
|
||
|
'50%'
|
||
|
],
|
||
|
size: [
|
||
|
'80%',
|
||
|
'80%'
|
||
|
],
|
||
|
root: '',
|
||
|
itemStyle: {
|
||
|
normal: {
|
||
|
label: {
|
||
|
show: true,
|
||
|
x: 5,
|
||
|
y: 12,
|
||
|
textStyle: {
|
||
|
align: 'left',
|
||
|
color: '#000',
|
||
|
fontFamily: 'Arial',
|
||
|
fontSize: 13,
|
||
|
fontStyle: 'normal',
|
||
|
fontWeight: 'normal'
|
||
|
}
|
||
|
},
|
||
|
breadcrumb: {
|
||
|
show: true,
|
||
|
textStyle: {}
|
||
|
},
|
||
|
borderWidth: 1,
|
||
|
borderColor: '#ccc',
|
||
|
childBorderWidth: 1,
|
||
|
childBorderColor: '#ccc'
|
||
|
},
|
||
|
emphasis: {}
|
||
|
}
|
||
|
};
|
||
|
var ecData = require('../util/ecData');
|
||
|
var zrConfig = require('zrender/config');
|
||
|
var zrEvent = require('zrender/tool/event');
|
||
|
var zrUtil = require('zrender/tool/util');
|
||
|
var zrColor = require('zrender/tool/color');
|
||
|
function Treemap(ecTheme, messageCenter, zr, option, myChart) {
|
||
|
ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart);
|
||
|
this.refresh(option);
|
||
|
var self = this;
|
||
|
self._onclick = function (params) {
|
||
|
return self.__onclick(params);
|
||
|
};
|
||
|
self.zr.on(zrConfig.EVENT.CLICK, self._onclick);
|
||
|
}
|
||
|
Treemap.prototype = {
|
||
|
type: ecConfig.CHART_TYPE_TREEMAP,
|
||
|
refresh: function (newOption) {
|
||
|
this.clear();
|
||
|
if (newOption) {
|
||
|
this.option = newOption;
|
||
|
this.series = this.option.series;
|
||
|
}
|
||
|
this._treesMap = {};
|
||
|
var series = this.series;
|
||
|
var legend = this.component.legend;
|
||
|
for (var i = 0; i < series.length; i++) {
|
||
|
if (series[i].type === ecConfig.CHART_TYPE_TREEMAP) {
|
||
|
series[i] = this.reformOption(series[i]);
|
||
|
var seriesName = series[i].name || '';
|
||
|
this.selectedMap[seriesName] = legend ? legend.isSelected(seriesName) : true;
|
||
|
if (!this.selectedMap[seriesName]) {
|
||
|
continue;
|
||
|
}
|
||
|
this._buildSeries(series[i], i);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
_buildSeries: function (series, seriesIndex) {
|
||
|
var tree = Tree.fromOptionData(series.name, series.data);
|
||
|
this._treesMap[seriesIndex] = tree;
|
||
|
var treeRoot = series.root && tree.getNodeById(series.root) || tree.root;
|
||
|
this._buildTreemap(treeRoot, seriesIndex);
|
||
|
},
|
||
|
_buildTreemap: function (treeRoot, seriesIndex) {
|
||
|
var shapeList = this.shapeList;
|
||
|
for (var i = 0; i < shapeList.length;) {
|
||
|
var shape = shapeList[i];
|
||
|
if (ecData.get(shape, 'seriesIndex') === seriesIndex) {
|
||
|
this.zr.delShape(shapeList[i]);
|
||
|
shapeList.splice(i, 1);
|
||
|
} else {
|
||
|
i++;
|
||
|
}
|
||
|
}
|
||
|
var currentShapeLen = shapeList.length;
|
||
|
var series = this.series[seriesIndex];
|
||
|
var itemStyle = series.itemStyle;
|
||
|
var treemapWidth = this.parsePercent(series.size[0], this.zr.getWidth()) || 400;
|
||
|
var treemapHeight = this.parsePercent(series.size[1], this.zr.getHeight()) || 500;
|
||
|
var center = this.parseCenter(this.zr, series.center);
|
||
|
var treemapX = center[0] - treemapWidth * 0.5;
|
||
|
var treemapY = center[1] - treemapHeight * 0.5;
|
||
|
var treemapArea = treemapWidth * treemapHeight;
|
||
|
var sum = 0;
|
||
|
var areaArr = [];
|
||
|
var children = treeRoot.children;
|
||
|
for (var i = 0; i < children.length; i++) {
|
||
|
sum += children[i].data.value;
|
||
|
}
|
||
|
for (var j = 0; j < children.length; j++) {
|
||
|
areaArr.push(children[j].data.value * treemapArea / sum);
|
||
|
}
|
||
|
var treeMapLayout = new TreeMapLayout({
|
||
|
x: treemapX,
|
||
|
y: treemapY,
|
||
|
width: treemapWidth,
|
||
|
height: treemapHeight
|
||
|
});
|
||
|
var locationArr = treeMapLayout.run(areaArr);
|
||
|
for (var k = 0; k < locationArr.length; k++) {
|
||
|
var dataItem = children[k].data;
|
||
|
var rect = locationArr[k];
|
||
|
var queryTarget = [
|
||
|
dataItem.itemStyle,
|
||
|
itemStyle
|
||
|
];
|
||
|
var itemStyleMerged = this.deepMerge(queryTarget);
|
||
|
if (!itemStyleMerged.normal.color) {
|
||
|
itemStyleMerged.normal.color = this.zr.getColor(k);
|
||
|
}
|
||
|
if (!itemStyleMerged.emphasis.color) {
|
||
|
itemStyleMerged.emphasis.color = itemStyleMerged.normal.color;
|
||
|
}
|
||
|
this._buildItem(dataItem, itemStyleMerged, rect, seriesIndex, k);
|
||
|
if (dataItem.children) {
|
||
|
this._buildChildrenTreemap(dataItem.children, itemStyleMerged, rect, seriesIndex);
|
||
|
}
|
||
|
}
|
||
|
if (this.query(series, 'itemStyle.normal.breadcrumb.show')) {
|
||
|
this._buildBreadcrumb(treeRoot, seriesIndex, treemapX, treemapY + treemapHeight);
|
||
|
}
|
||
|
for (var i = currentShapeLen; i < shapeList.length; i++) {
|
||
|
this.zr.addShape(shapeList[i]);
|
||
|
}
|
||
|
},
|
||
|
_buildItem: function (dataItem, itemStyle, rect, seriesIndex, dataIndex) {
|
||
|
var series = this.series;
|
||
|
var rectangle = this.getRectangle(dataItem, itemStyle, rect);
|
||
|
ecData.pack(rectangle, series[seriesIndex], seriesIndex, dataItem, dataIndex, dataItem.name);
|
||
|
this.shapeList.push(rectangle);
|
||
|
},
|
||
|
getRectangle: function (dataItem, itemStyle, rect) {
|
||
|
var emphasis = itemStyle.emphasis;
|
||
|
var normal = itemStyle.normal;
|
||
|
var textShape = this.getLabel(itemStyle, rect, dataItem.name, dataItem.value);
|
||
|
var hoverable = this.option.hoverable;
|
||
|
var rectangleShape = {
|
||
|
zlevel: this.getZlevelBase(),
|
||
|
z: this.getZBase(),
|
||
|
hoverable: hoverable,
|
||
|
clickable: true,
|
||
|
style: zrUtil.merge({
|
||
|
x: rect.x,
|
||
|
y: rect.y,
|
||
|
width: rect.width,
|
||
|
height: rect.height,
|
||
|
brushType: 'both',
|
||
|
color: normal.color,
|
||
|
lineWidth: normal.borderWidth,
|
||
|
strokeColor: normal.borderColor
|
||
|
}, textShape.style, true),
|
||
|
highlightStyle: zrUtil.merge({
|
||
|
color: emphasis.color,
|
||
|
lineWidth: emphasis.borderWidth,
|
||
|
strokeColor: emphasis.borderColor
|
||
|
}, textShape.highlightStyle, true)
|
||
|
};
|
||
|
return new RectangleShape(rectangleShape);
|
||
|
},
|
||
|
getLabel: function (itemStyle, rect, name, value) {
|
||
|
var normalTextStyle = itemStyle.normal.label.textStyle;
|
||
|
var queryTarget = [
|
||
|
itemStyle.emphasis.label.textStyle,
|
||
|
normalTextStyle
|
||
|
];
|
||
|
var emphasisTextStyle = this.deepMerge(queryTarget);
|
||
|
var formatter = itemStyle.normal.label.formatter;
|
||
|
var text = this.getLabelText(name, value, formatter);
|
||
|
var textFont = this.getFont(normalTextStyle);
|
||
|
var textWidth = toolArea.getTextWidth(text, textFont);
|
||
|
var textHeight = toolArea.getTextHeight(text, textFont);
|
||
|
var emphasisFormatter = this.deepQuery([
|
||
|
itemStyle.emphasis,
|
||
|
itemStyle.normal
|
||
|
], 'label.formatter');
|
||
|
var emphasisText = this.getLabelText(name, value, emphasisFormatter);
|
||
|
var emphasisTextFont = this.getFont(emphasisTextStyle);
|
||
|
var emphasisTextWidth = toolArea.getTextWidth(text, emphasisTextFont);
|
||
|
var emphasisTextHeight = toolArea.getTextHeight(text, emphasisTextFont);
|
||
|
if (!itemStyle.normal.label.show) {
|
||
|
text = '';
|
||
|
} else if (itemStyle.normal.label.x + textWidth > rect.width || itemStyle.normal.label.y + textHeight > rect.height) {
|
||
|
text = '';
|
||
|
}
|
||
|
if (!itemStyle.emphasis.label.show) {
|
||
|
emphasisText = '';
|
||
|
} else if (emphasisTextStyle.x + emphasisTextWidth > rect.width || emphasisTextStyle.y + emphasisTextHeight > rect.height) {
|
||
|
emphasisText = '';
|
||
|
}
|
||
|
var textShape = {
|
||
|
style: {
|
||
|
textX: rect.x + itemStyle.normal.label.x,
|
||
|
textY: rect.y + itemStyle.normal.label.y,
|
||
|
text: text,
|
||
|
textPosition: 'specific',
|
||
|
textColor: normalTextStyle.color,
|
||
|
textFont: textFont
|
||
|
},
|
||
|
highlightStyle: {
|
||
|
textX: rect.x + itemStyle.emphasis.label.x,
|
||
|
textY: rect.y + itemStyle.emphasis.label.y,
|
||
|
text: emphasisText,
|
||
|
textColor: emphasisTextStyle.color,
|
||
|
textPosition: 'specific'
|
||
|
}
|
||
|
};
|
||
|
return textShape;
|
||
|
},
|
||
|
getLabelText: function (name, value, formatter) {
|
||
|
if (formatter) {
|
||
|
if (typeof formatter === 'function') {
|
||
|
return formatter.call(this.myChart, name, value);
|
||
|
} else if (typeof formatter === 'string') {
|
||
|
formatter = formatter.replace('{b}', '{b0}').replace('{c}', '{c0}');
|
||
|
formatter = formatter.replace('{b0}', name).replace('{c0}', value);
|
||
|
return formatter;
|
||
|
}
|
||
|
} else {
|
||
|
return name;
|
||
|
}
|
||
|
},
|
||
|
_buildChildrenTreemap: function (data, itemStyle, rect, seriesIndex) {
|
||
|
var treemapArea = rect.width * rect.height;
|
||
|
var sum = 0;
|
||
|
var areaArr = [];
|
||
|
for (var i = 0; i < data.length; i++) {
|
||
|
sum += data[i].value;
|
||
|
}
|
||
|
for (var j = 0; j < data.length; j++) {
|
||
|
areaArr.push(data[j].value * treemapArea / sum);
|
||
|
}
|
||
|
var treeMapLayout = new TreeMapLayout({
|
||
|
x: rect.x,
|
||
|
y: rect.y,
|
||
|
width: rect.width,
|
||
|
height: rect.height
|
||
|
});
|
||
|
var locationArr = treeMapLayout.run(areaArr);
|
||
|
var lineWidth = itemStyle.normal.childBorderWidth;
|
||
|
var lineColor = itemStyle.normal.childBorderColor;
|
||
|
for (var k = 0; k < locationArr.length; k++) {
|
||
|
var item = locationArr[k];
|
||
|
var lines = [];
|
||
|
if (rect.y.toFixed(2) !== item.y.toFixed(2)) {
|
||
|
lines.push(this._getLine(item.x, item.y, item.x + item.width, item.y, lineWidth, lineColor));
|
||
|
}
|
||
|
if (rect.x.toFixed(2) !== item.x.toFixed(2)) {
|
||
|
lines.push(this._getLine(item.x, item.y, item.x, item.y + item.height, lineWidth, lineColor));
|
||
|
}
|
||
|
if ((rect.y + rect.height).toFixed(2) !== (item.y + item.height).toFixed(2)) {
|
||
|
lines.push(this._getLine(item.x, item.y + item.height, item.x + item.width, item.y + item.height, lineWidth, lineColor));
|
||
|
}
|
||
|
if ((rect.x + rect.width).toFixed(2) !== (item.x + item.width).toFixed(2)) {
|
||
|
lines.push(this._getLine(item.x + item.width, item.y, item.x + item.width, item.y + item.height, lineWidth, lineColor));
|
||
|
}
|
||
|
for (var l = 0; l < lines.length; l++) {
|
||
|
ecData.set(lines[l], 'seriesIndex', seriesIndex);
|
||
|
this.shapeList.push(lines[l]);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
_getLine: function (xStart, yStart, xEnd, yEnd, lineWidth, lineColor) {
|
||
|
var lineShape = {
|
||
|
zlevel: this.getZlevelBase(),
|
||
|
z: this.getZBase(),
|
||
|
hoverable: false,
|
||
|
style: {
|
||
|
xStart: xStart,
|
||
|
yStart: yStart,
|
||
|
xEnd: xEnd,
|
||
|
yEnd: yEnd,
|
||
|
lineWidth: lineWidth,
|
||
|
strokeColor: lineColor
|
||
|
}
|
||
|
};
|
||
|
return new LineShape(lineShape);
|
||
|
},
|
||
|
_buildBreadcrumb: function (treeRoot, seriesIndex, x, y) {
|
||
|
var stack = [];
|
||
|
var current = treeRoot;
|
||
|
while (current) {
|
||
|
stack.unshift(current.data.name);
|
||
|
current = current.parent;
|
||
|
}
|
||
|
var series = this.series[seriesIndex];
|
||
|
var textStyle = this.query(series, 'itemStyle.normal.breadcrumb.textStyle') || {};
|
||
|
var textEmphasisStyle = this.query(series, 'itemStyle.emphasis.breadcrumb.textStyle') || {};
|
||
|
var commonStyle = {
|
||
|
y: y + 10,
|
||
|
textBaseline: 'top',
|
||
|
textAlign: 'left',
|
||
|
color: textStyle.color,
|
||
|
textFont: this.getFont(textStyle)
|
||
|
};
|
||
|
var commonHighlightStyle = {
|
||
|
brushType: 'fill',
|
||
|
color: textEmphasisStyle.color || zrColor.lift(textStyle.color, -0.3),
|
||
|
textFont: this.getFont(textEmphasisStyle)
|
||
|
};
|
||
|
for (var i = 0; i < stack.length; i++) {
|
||
|
var textShape = new TextShape({
|
||
|
zlevel: this.getZlevelBase(),
|
||
|
z: this.getZBase(),
|
||
|
style: zrUtil.merge({
|
||
|
x: x,
|
||
|
text: stack[i] + (stack.length - 1 - i ? ' > ' : '')
|
||
|
}, commonStyle),
|
||
|
clickable: true,
|
||
|
highlightStyle: commonHighlightStyle
|
||
|
});
|
||
|
ecData.set(textShape, 'seriesIndex', seriesIndex);
|
||
|
ecData.set(textShape, 'name', stack[i]);
|
||
|
x += textShape.getRect(textShape.style).width;
|
||
|
this.shapeList.push(textShape);
|
||
|
}
|
||
|
},
|
||
|
__onclick: function (params) {
|
||
|
var target = params.target;
|
||
|
if (target) {
|
||
|
var seriesIndex = ecData.get(target, 'seriesIndex');
|
||
|
var name = ecData.get(target, 'name');
|
||
|
var tree = this._treesMap[seriesIndex];
|
||
|
var root = tree.getNodeById(name);
|
||
|
if (root && root.children.length) {
|
||
|
this._buildTreemap(root, seriesIndex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
zrUtil.inherits(Treemap, ChartBase);
|
||
|
require('../chart').define('treemap', Treemap);
|
||
|
return Treemap;
|
||
|
});define('echarts/layout/TreeMap', ['require'], function (require) {
|
||
|
function TreeMapLayout(opts) {
|
||
|
var row = {
|
||
|
x: opts.x,
|
||
|
y: opts.y,
|
||
|
width: opts.width,
|
||
|
height: opts.height
|
||
|
};
|
||
|
this.x = opts.x;
|
||
|
this.y = opts.y;
|
||
|
this.width = opts.width;
|
||
|
this.height = opts.height;
|
||
|
}
|
||
|
TreeMapLayout.prototype.run = function (areas) {
|
||
|
var out = [];
|
||
|
this._squarify(areas, {
|
||
|
x: this.x,
|
||
|
y: this.y,
|
||
|
width: this.width,
|
||
|
height: this.height
|
||
|
}, out);
|
||
|
return out;
|
||
|
};
|
||
|
TreeMapLayout.prototype._squarify = function (areas, row, out) {
|
||
|
var layoutDirection = 'VERTICAL';
|
||
|
var width = row.width;
|
||
|
var height = row.height;
|
||
|
if (row.width < row.height) {
|
||
|
layoutDirection = 'HORIZONTAL';
|
||
|
width = row.height;
|
||
|
height = row.width;
|
||
|
}
|
||
|
var shapeArr = this._getShapeListInAbstractRow(areas, width, height);
|
||
|
for (var i = 0; i < shapeArr.length; i++) {
|
||
|
shapeArr[i].x = 0;
|
||
|
shapeArr[i].y = 0;
|
||
|
for (var j = 0; j < i; j++) {
|
||
|
shapeArr[i].y += shapeArr[j].height;
|
||
|
}
|
||
|
}
|
||
|
var nextRow = {};
|
||
|
if (layoutDirection == 'VERTICAL') {
|
||
|
for (var k = 0; k < shapeArr.length; k++) {
|
||
|
out.push({
|
||
|
x: shapeArr[k].x + row.x,
|
||
|
y: shapeArr[k].y + row.y,
|
||
|
width: shapeArr[k].width,
|
||
|
height: shapeArr[k].height
|
||
|
});
|
||
|
}
|
||
|
nextRow = {
|
||
|
x: shapeArr[0].width + row.x,
|
||
|
y: row.y,
|
||
|
width: row.width - shapeArr[0].width,
|
||
|
height: row.height
|
||
|
};
|
||
|
} else {
|
||
|
for (var l = 0; l < shapeArr.length; l++) {
|
||
|
out.push({
|
||
|
x: shapeArr[l].y + row.x,
|
||
|
y: shapeArr[l].x + row.y,
|
||
|
width: shapeArr[l].height,
|
||
|
height: shapeArr[l].width
|
||
|
});
|
||
|
}
|
||
|
nextRow = {
|
||
|
x: row.x,
|
||
|
y: row.y + shapeArr[0].width,
|
||
|
width: row.width,
|
||
|
height: row.height - shapeArr[0].width
|
||
|
};
|
||
|
}
|
||
|
var nextAreaArr = areas.slice(shapeArr.length);
|
||
|
if (nextAreaArr.length === 0) {
|
||
|
return;
|
||
|
} else {
|
||
|
this._squarify(nextAreaArr, nextRow, out);
|
||
|
}
|
||
|
};
|
||
|
TreeMapLayout.prototype._getShapeListInAbstractRow = function (areas, width, height) {
|
||
|
if (areas.length === 1) {
|
||
|
return [{
|
||
|
width: width,
|
||
|
height: height
|
||
|
}];
|
||
|
}
|
||
|
for (var count = 1; count < areas.length; count++) {
|
||
|
var shapeArr0 = this._placeFixedNumberRectangles(areas.slice(0, count), width, height);
|
||
|
var shapeArr1 = this._placeFixedNumberRectangles(areas.slice(0, count + 1), width, height);
|
||
|
if (this._isFirstBetter(shapeArr0, shapeArr1)) {
|
||
|
return shapeArr0;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
TreeMapLayout.prototype._placeFixedNumberRectangles = function (areaSubArr, width, height) {
|
||
|
var count = areaSubArr.length;
|
||
|
var shapeArr = [];
|
||
|
var sum = 0;
|
||
|
for (var i = 0; i < areaSubArr.length; i++) {
|
||
|
sum += areaSubArr[i];
|
||
|
}
|
||
|
var cellWidth = sum / height;
|
||
|
for (var j = 0; j < count; j++) {
|
||
|
var cellHeight = height * areaSubArr[j] / sum;
|
||
|
shapeArr.push({
|
||
|
width: cellWidth,
|
||
|
height: cellHeight
|
||
|
});
|
||
|
}
|
||
|
return shapeArr;
|
||
|
};
|
||
|
TreeMapLayout.prototype._isFirstBetter = function (shapeArr0, shapeArr1) {
|
||
|
var ratio0 = shapeArr0[0].height / shapeArr0[0].width;
|
||
|
ratio0 = ratio0 > 1 ? 1 / ratio0 : ratio0;
|
||
|
var ratio1 = shapeArr1[0].height / shapeArr1[0].width;
|
||
|
ratio1 = ratio1 > 1 ? 1 / ratio1 : ratio1;
|
||
|
if (Math.abs(ratio0 - 1) <= Math.abs(ratio1 - 1)) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
return TreeMapLayout;
|
||
|
});define('echarts/data/Tree', [
|
||
|
'require',
|
||
|
'zrender/tool/util'
|
||
|
], function (require) {
|
||
|
var zrUtil = require('zrender/tool/util');
|
||
|
function TreeNode(id, data) {
|
||
|
this.id = id;
|
||
|
this.depth = 0;
|
||
|
this.height = 0;
|
||
|
this.children = [];
|
||
|
this.parent = null;
|
||
|
this.data = data || null;
|
||
|
}
|
||
|
TreeNode.prototype.add = function (child) {
|
||
|
var children = this.children;
|
||
|
if (child.parent === this) {
|
||
|
return;
|
||
|
}
|
||
|
children.push(child);
|
||
|
child.parent = this;
|
||
|
};
|
||
|
TreeNode.prototype.remove = function (child) {
|
||
|
var children = this.children;
|
||
|
var idx = zrUtil.indexOf(children, child);
|
||
|
if (idx >= 0) {
|
||
|
children.splice(idx, 1);
|
||
|
child.parent = null;
|
||
|
}
|
||
|
};
|
||
|
TreeNode.prototype.traverse = function (cb, context) {
|
||
|
cb.call(context, this);
|
||
|
for (var i = 0; i < this.children.length; i++) {
|
||
|
this.children[i].traverse(cb, context);
|
||
|
}
|
||
|
};
|
||
|
TreeNode.prototype.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;
|
||
|
};
|
||
|
TreeNode.prototype.getNodeById = function (id) {
|
||
|
if (this.id === id) {
|
||
|
return this;
|
||
|
}
|
||
|
for (var i = 0; i < this.children.length; i++) {
|
||
|
var res = this.children[i].getNodeById(id);
|
||
|
if (res) {
|
||
|
return res;
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
function Tree(id) {
|
||
|
this.root = new TreeNode(id);
|
||
|
}
|
||
|
Tree.prototype.traverse = function (cb, context) {
|
||
|
this.root.traverse(cb, context);
|
||
|
};
|
||
|
Tree.prototype.getSubTree = function (id) {
|
||
|
var root = this.getNodeById(id);
|
||
|
if (root) {
|
||
|
var tree = new Tree(root.id);
|
||
|
tree.root = root;
|
||
|
return tree;
|
||
|
}
|
||
|
};
|
||
|
Tree.prototype.getNodeById = function (id) {
|
||
|
return this.root.getNodeById(id);
|
||
|
};
|
||
|
Tree.fromOptionData = function (id, data) {
|
||
|
var tree = new Tree(id);
|
||
|
var rootNode = tree.root;
|
||
|
rootNode.data = {
|
||
|
name: id,
|
||
|
children: data
|
||
|
};
|
||
|
function buildHierarchy(dataNode, parentNode) {
|
||
|
var node = new TreeNode(dataNode.name, dataNode);
|
||
|
parentNode.add(node);
|
||
|
var children = dataNode.children;
|
||
|
if (children) {
|
||
|
for (var i = 0; i < children.length; i++) {
|
||
|
buildHierarchy(children[i], node);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for (var i = 0; i < data.length; i++) {
|
||
|
buildHierarchy(data[i], rootNode);
|
||
|
}
|
||
|
tree.root.updateDepthAndHeight(0);
|
||
|
return tree;
|
||
|
};
|
||
|
Tree.fromGraph = function (graph) {
|
||
|
function buildHierarchy(root) {
|
||
|
var graphNode = graph.getNodeById(root.id);
|
||
|
for (var i = 0; i < graphNode.outEdges.length; i++) {
|
||
|
var edge = graphNode.outEdges[i];
|
||
|
var childTreeNode = treeNodesMap[edge.node2.id];
|
||
|
root.children.push(childTreeNode);
|
||
|
buildHierarchy(childTreeNode);
|
||
|
}
|
||
|
}
|
||
|
var treeMap = {};
|
||
|
var treeNodesMap = {};
|
||
|
for (var i = 0; i < graph.nodes.length; i++) {
|
||
|
var node = graph.nodes[i];
|
||
|
var treeNode;
|
||
|
if (node.inDegree() === 0) {
|
||
|
treeMap[node.id] = new Tree(node.id);
|
||
|
treeNode = treeMap[node.id].root;
|
||
|
} else {
|
||
|
treeNode = new TreeNode(node.id);
|
||
|
}
|
||
|
treeNode.data = node.data;
|
||
|
treeNodesMap[node.id] = treeNode;
|
||
|
}
|
||
|
var treeList = [];
|
||
|
for (var id in treeMap) {
|
||
|
buildHierarchy(treeMap[id].root);
|
||
|
treeMap[id].root.updateDepthAndHeight(0);
|
||
|
treeList.push(treeMap[id]);
|
||
|
}
|
||
|
return treeList;
|
||
|
};
|
||
|
return Tree;
|
||
|
});
|