/**
|
* echarts图表类:树图
|
*
|
* @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。
|
* @author Loutongbing (娄同兵, loutongbing@126.com)
|
*/
|
|
define(function (require) {
|
var ChartBase = require('./base');
|
var GOLDEN_SECTION = 0.618;
|
// 图形依赖
|
var IconShape = require('../util/shape/Icon');
|
var ImageShape = require('zrender/shape/Image');
|
var LineShape = require('zrender/shape/Line');
|
var BezierCurveShape = require('zrender/shape/BezierCurve');
|
// 布局依赖
|
var TreeLayout = require('../layout/Tree');
|
// 数据依赖
|
var TreeData = require('../data/Tree');
|
|
var ecConfig = require('../config');
|
// 默认参数
|
ecConfig.tree = {
|
zlevel: 1, // 一级层叠
|
z: 2, // 二级层叠
|
calculable: false,
|
clickable: true,
|
rootLocation: {},
|
orient: 'vertical',
|
symbol: 'circle',
|
symbolSize: 20,
|
nodePadding: 30,
|
layerPadding: 100,
|
/*rootLocation: {
|
x: 'center' | 'left' | 'right' | 'x%' | {number},
|
y: 'center' | 'top' | 'bottom' | 'y%' | {number}
|
},*/
|
itemStyle: {
|
normal: {
|
// color: 各异,
|
label: {
|
show: true
|
},
|
lineStyle: {
|
width: 1,
|
color: '#777',
|
type: 'curve' // curve
|
}
|
},
|
emphasis: {
|
|
}
|
}
|
};
|
|
var ecData = require('../util/ecData');
|
var zrConfig = require('zrender/config');
|
var zrEvent = require('zrender/tool/event');
|
var zrUtil = require('zrender/tool/util');
|
|
/**
|
* 构造函数
|
* @param {Object} messageCenter echart消息中心
|
* @param {ZRender} zr zrender实例
|
* @param {Object} series 数据
|
* @param {Object} component 组件
|
* @constructor
|
* @exports Tree
|
*/
|
function Tree(ecTheme, messageCenter, zr, option, myChart) {
|
// 图表基类
|
ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart);
|
this.refresh(option);
|
}
|
Tree.prototype = {
|
type : ecConfig.CHART_TYPE_TREE,
|
/**
|
* 构建单个
|
*
|
* @param {Object} data 数据
|
*/
|
_buildShape : function (series, seriesIndex) {
|
var data = series.data[0];
|
this.tree = TreeData.fromOptionData(data.name, data.children);
|
// 添加root的data
|
this.tree.root.data = data;
|
// 根据root坐标 方向 对每个节点的坐标进行映射
|
this._setTreeShape(series);
|
// 递归画出树节点与连接线
|
this.tree.traverse(
|
function (treeNode) {
|
this._buildItem(
|
treeNode,
|
series,
|
seriesIndex
|
);
|
// 画连接线
|
if (treeNode.children.length > 0) {
|
this._buildLink(
|
treeNode,
|
series
|
);
|
}
|
},
|
this
|
);
|
var panable = series.roam === true || series.roam === 'move';
|
var zoomable = series.roam === true || series.roam === 'scale';
|
// Enable pan and zooom
|
this.zr.modLayer(this.getZlevelBase(), {
|
panable: panable,
|
zoomable: zoomable
|
});
|
if (
|
this.query('markPoint.effect.show')
|
|| this.query('markLine.effect.show')
|
) {
|
this.zr.modLayer(ecConfig.EFFECT_ZLEVEL, {
|
panable: panable,
|
zoomable: zoomable
|
});
|
}
|
this.addShapeList();
|
},
|
|
/**
|
* 构建单个item
|
*/
|
_buildItem : function (
|
treeNode,
|
serie,
|
seriesIndex
|
) {
|
var queryTarget = [treeNode.data, serie];
|
var symbol = this.deepQuery(queryTarget, 'symbol');
|
// 多级控制
|
var normal = this.deepMerge(
|
queryTarget,
|
'itemStyle.normal'
|
) || {};
|
var emphasis = this.deepMerge(
|
queryTarget,
|
'itemStyle.emphasis'
|
) || {};
|
var normalColor = normal.color || this.zr.getColor();
|
var emphasisColor = emphasis.color || this.zr.getColor();
|
var angle = -treeNode.layout.angle || 0;
|
// 根节点不旋转
|
if (treeNode.id === this.tree.root.id) {
|
angle = 0;
|
}
|
var textPosition = 'right';
|
if (Math.abs(angle) >= Math.PI / 2 && Math.abs(angle) < Math.PI * 3 / 2) {
|
angle += Math.PI;
|
textPosition = 'left';
|
}
|
var rotation = [
|
angle,
|
treeNode.layout.position[0],
|
treeNode.layout.position[1]
|
];
|
var shape = new IconShape({
|
zlevel: this.getZlevelBase(),
|
z: this.getZBase() + 1,
|
rotation: rotation,
|
clickable: this.deepQuery(queryTarget, 'clickable'),
|
style: {
|
x: treeNode.layout.position[0] - treeNode.layout.width * 0.5,
|
y: treeNode.layout.position[1] - treeNode.layout.height * 0.5,
|
width: treeNode.layout.width,
|
height: treeNode.layout.height,
|
iconType: symbol,
|
color: normalColor,
|
brushType: 'both',
|
lineWidth: normal.borderWidth,
|
strokeColor: normal.borderColor
|
},
|
highlightStyle: {
|
color: emphasisColor,
|
lineWidth: emphasis.borderWidth,
|
strokeColor: emphasis.borderColor
|
}
|
});
|
if (shape.style.iconType.match('image')) {
|
shape.style.image = shape.style.iconType.replace(
|
new RegExp('^image:\\/\\/'), ''
|
);
|
shape = new ImageShape({
|
rotation: rotation,
|
style: shape.style,
|
highlightStyle: shape.highlightStyle,
|
clickable: shape.clickable,
|
zlevel: this.getZlevelBase(),
|
z: this.getZBase()
|
});
|
}
|
// 节点标签样式
|
if (this.deepQuery(queryTarget, 'itemStyle.normal.label.show')) {
|
shape.style.text = treeNode.data.label == null ? treeNode.id : treeNode.data.label;
|
shape.style.textPosition = this.deepQuery(
|
queryTarget, 'itemStyle.normal.label.position'
|
);
|
// 极坐标另外计算 时钟哪个侧面
|
if (serie.orient === 'radial' && shape.style.textPosition !== 'inside') {
|
shape.style.textPosition = textPosition;
|
}
|
shape.style.textColor = this.deepQuery(
|
queryTarget, 'itemStyle.normal.label.textStyle.color'
|
);
|
shape.style.textFont = this.getFont(this.deepQuery(
|
queryTarget, 'itemStyle.normal.label.textStyle'
|
) || {});
|
}
|
|
if (this.deepQuery(queryTarget, 'itemStyle.emphasis.label.show')) {
|
shape.highlightStyle.textPosition = this.deepQuery(
|
queryTarget, 'itemStyle.emphasis.label.position'
|
);
|
shape.highlightStyle.textColor = this.deepQuery(
|
queryTarget, 'itemStyle.emphasis.label.textStyle.color'
|
);
|
shape.highlightStyle.textFont = this.getFont(this.deepQuery(
|
queryTarget, 'itemStyle.emphasis.label.textStyle'
|
) || {});
|
}
|
// todo
|
ecData.pack(
|
shape,
|
serie, seriesIndex,
|
treeNode.data, 0,
|
treeNode.id
|
);
|
this.shapeList.push(shape);
|
},
|
|
_buildLink : function (
|
parentNode,
|
serie
|
) {
|
var lineStyle = serie.itemStyle.normal.lineStyle;
|
// 折线另外计算
|
if (lineStyle.type === 'broken') {
|
this._buildBrokenLine(
|
parentNode,
|
lineStyle,
|
serie
|
);
|
return;
|
}
|
for (var i = 0; i < parentNode.children.length; i++) {
|
var xStart = parentNode.layout.position[0];
|
var yStart = parentNode.layout.position[1];
|
var xEnd = parentNode.children[i].layout.position[0];
|
var yEnd = parentNode.children[i].layout.position[1];
|
switch (lineStyle.type) {
|
case 'curve':
|
this._buildBezierCurve(
|
parentNode,
|
parentNode.children[i],
|
lineStyle,
|
serie
|
);
|
break;
|
// 折线
|
case 'broken':
|
break;
|
// default画直线
|
default:
|
var shape = this._getLine(
|
xStart,
|
yStart,
|
xEnd,
|
yEnd,
|
lineStyle
|
);
|
this.shapeList.push(shape);
|
}
|
}
|
},
|
_buildBrokenLine: function (
|
parentNode,
|
lineStyle,
|
serie
|
) {
|
// 引用_getLine需要把type改为solid
|
var solidLineStyle = zrUtil.clone(lineStyle);
|
solidLineStyle.type = 'solid';
|
var shapes = [];
|
var xStart = parentNode.layout.position[0];
|
var yStart = parentNode.layout.position[1];
|
var orient = serie.orient;
|
|
// 子节点的y
|
var yEnd = parentNode.children[0].layout.position[1];
|
// 中点x y
|
var xMiddle = xStart;
|
var yMiddle = yStart + (yEnd - yStart) * (1 - GOLDEN_SECTION);
|
// 中线的起始
|
var xMiddleStart = parentNode.children[0].layout.position[0];
|
var yMiddleStart = yMiddle;
|
var xMiddleEnd = parentNode.children[parentNode.children.length - 1].layout.position[0];
|
var yMiddleEnd = yMiddle;
|
// 水平状态
|
if (orient === 'horizontal') {
|
var xEnd = parentNode.children[0].layout.position[0];
|
xMiddle = xStart + (xEnd - xStart) * (1 - GOLDEN_SECTION);
|
yMiddle = yStart;
|
xMiddleStart = xMiddle;
|
yMiddleStart = parentNode.children[0].layout.position[1];
|
xMiddleEnd = xMiddle;
|
yMiddleEnd = parentNode.children[parentNode.children.length - 1].layout.position[1];
|
}
|
// 第一条 从根节点垂直向下
|
shapes.push(
|
this._getLine(
|
xStart,
|
yStart,
|
xMiddle,
|
yMiddle,
|
solidLineStyle
|
)
|
);
|
// 第二条 横向
|
shapes.push(
|
this._getLine(
|
xMiddleStart,
|
yMiddleStart,
|
xMiddleEnd,
|
yMiddleEnd,
|
solidLineStyle
|
)
|
);
|
// 第三条 垂直向下到子节点
|
for (var i = 0; i < parentNode.children.length; i++) {
|
xEnd = parentNode.children[i].layout.position[0];
|
yEnd = parentNode.children[i].layout.position[1];
|
// 水平状态
|
if (orient === 'horizontal') {
|
yMiddleStart = yEnd;
|
}
|
else {
|
xMiddleStart = xEnd;
|
}
|
shapes.push(
|
this._getLine(
|
xMiddleStart,
|
yMiddleStart,
|
xEnd,
|
yEnd,
|
solidLineStyle
|
)
|
);
|
}
|
this.shapeList = this.shapeList.concat(shapes);
|
},
|
_getLine: function (
|
xStart,
|
yStart,
|
xEnd,
|
yEnd,
|
lineStyle
|
) {
|
if (xStart === xEnd) {
|
xStart = xEnd = this.subPixelOptimize(xStart, lineStyle.width);
|
}
|
if (yStart === yEnd) {
|
yStart = yEnd = this.subPixelOptimize(yStart, lineStyle.width);
|
}
|
return new LineShape({
|
zlevel: this.getZlevelBase(),
|
hoverable: false,
|
style: zrUtil.merge(
|
{
|
xStart: xStart,
|
yStart: yStart,
|
xEnd: xEnd,
|
yEnd: yEnd,
|
lineType: lineStyle.type,
|
strokeColor: lineStyle.color,
|
lineWidth: lineStyle.width
|
},
|
lineStyle,
|
true
|
)
|
});
|
},
|
_buildBezierCurve: function (
|
parentNode,
|
treeNode,
|
lineStyle,
|
serie
|
) {
|
var offsetRatio = GOLDEN_SECTION;
|
var orient = serie.orient;
|
var xStart = parentNode.layout.position[0];
|
var yStart = parentNode.layout.position[1];
|
var xEnd = treeNode.layout.position[0];
|
var yEnd = treeNode.layout.position[1];
|
var cpX1 = xStart;
|
var cpY1 = (yEnd - yStart) * offsetRatio + yStart;
|
var cpX2 = xEnd;
|
var cpY2 = (yEnd - yStart) * (1 - offsetRatio) + yStart;
|
if (orient === 'horizontal') {
|
cpX1 = (xEnd - xStart) * offsetRatio + xStart;
|
cpY1 = yStart;
|
cpX2 = (xEnd - xStart) * (1 - offsetRatio) + xStart;
|
cpY2 = yEnd;
|
}
|
else if (orient === 'radial') {
|
// 根节点 画直线
|
if (parentNode.id === this.tree.root.id) {
|
cpX1 = (xEnd - xStart) * offsetRatio + xStart;
|
cpY1 = (yEnd - yStart) * offsetRatio + yStart;
|
cpX2 = (xEnd - xStart) * (1 - offsetRatio) + xStart;
|
cpY2 = (yEnd - yStart) * (1 - offsetRatio) + yStart;
|
}
|
else {
|
var xStartOrigin = parentNode.layout.originPosition[0];
|
var yStartOrigin = parentNode.layout.originPosition[1];
|
var xEndOrigin = treeNode.layout.originPosition[0];
|
var yEndOrigin = treeNode.layout.originPosition[1];
|
var rootX = this.tree.root.layout.position[0];
|
var rootY = this.tree.root.layout.position[1];
|
|
cpX1 = xStartOrigin;
|
cpY1 = (yEndOrigin - yStartOrigin) * offsetRatio + yStartOrigin;
|
cpX2 = xEndOrigin;
|
cpY2 = (yEndOrigin - yStartOrigin) * (1 - offsetRatio) + yStartOrigin;
|
var rad = (cpX1 - this.minX) / this.width * Math.PI * 2;
|
cpX1 = cpY1 * Math.cos(rad) + rootX;
|
cpY1 = cpY1 * Math.sin(rad) + rootY;
|
|
rad = (cpX2 - this.minX) / this.width * Math.PI * 2;
|
cpX2 = cpY2 * Math.cos(rad) + rootX;
|
cpY2 = cpY2 * Math.sin(rad) + rootY;
|
}
|
}
|
var shape = new BezierCurveShape({
|
zlevel: this.getZlevelBase(),
|
hoverable: false,
|
style: zrUtil.merge(
|
{
|
xStart: xStart,
|
yStart: yStart,
|
cpX1: cpX1,
|
cpY1: cpY1,
|
cpX2: cpX2,
|
cpY2: cpY2,
|
xEnd: xEnd,
|
yEnd: yEnd,
|
strokeColor: lineStyle.color,
|
lineWidth: lineStyle.width
|
},
|
lineStyle,
|
true
|
)
|
});
|
this.shapeList.push(shape);
|
},
|
_setTreeShape : function (serie) {
|
// 跑出来树的layout
|
var treeLayout = new TreeLayout(
|
{
|
nodePadding: serie.nodePadding,
|
layerPadding: serie.layerPadding
|
}
|
);
|
this.tree.traverse(
|
function (treeNode) {
|
var queryTarget = [treeNode.data, serie];
|
var symbolSize = this.deepQuery(queryTarget, 'symbolSize');
|
if (typeof symbolSize === 'number') {
|
symbolSize = [symbolSize, symbolSize];
|
}
|
treeNode.layout = {
|
width: symbolSize[0], // 节点大小
|
height: symbolSize[1]
|
};
|
},
|
this
|
);
|
treeLayout.run(this.tree);
|
// 树的方向
|
var orient = serie.orient;
|
var rootX = serie.rootLocation.x;
|
var rootY = serie.rootLocation.y;
|
var zrWidth = this.zr.getWidth();
|
var zrHeight = this.zr.getHeight();
|
if (rootX === 'center') {
|
rootX = zrWidth * 0.5;
|
}
|
else {
|
rootX = this.parsePercent(rootX, zrWidth);
|
}
|
if (rootY === 'center') {
|
rootY = zrHeight * 0.5;
|
}
|
else {
|
rootY = this.parsePercent(rootY, zrHeight);
|
}
|
rootY = this.parsePercent(rootY, zrHeight);
|
// 水平树
|
if (orient === 'horizontal') {
|
rootX = isNaN(rootX) ? 10 : rootX;
|
rootY = isNaN(rootY) ? zrHeight * 0.5 : rootY;
|
}
|
// 极坐标
|
if (orient === 'radial') {
|
rootX = isNaN(rootX) ? zrWidth * 0.5 : rootX;
|
rootY = isNaN(rootY) ? zrHeight * 0.5 : rootY;
|
}
|
// 纵向树
|
else {
|
rootX = isNaN(rootX) ? zrWidth * 0.5 : rootX;
|
rootY = isNaN(rootY) ? 10 : rootY;
|
}
|
// tree layout自动算出来的root的坐标
|
var originRootX = this.tree.root.layout.position[0];
|
// 若是极坐标,则求最大最小值
|
if (orient === 'radial') {
|
var minX = Infinity;
|
var maxX = 0;
|
var maxWidth = 0;
|
this.tree.traverse(
|
function (treeNode) {
|
maxX = Math.max(maxX, treeNode.layout.position[0]);
|
minX = Math.min(minX, treeNode.layout.position[0]);
|
maxWidth = Math.max(maxWidth, treeNode.layout.width);
|
}
|
);
|
// 2 * maxWidth 还不大好但至少保证绝不会重叠 todo
|
this.width = maxX - minX + 2 * maxWidth;
|
this.minX = minX;
|
}
|
this.tree.traverse(
|
function (treeNode) {
|
var x;
|
var y;
|
if (orient === 'vertical' && serie.direction === 'inverse') {
|
x = treeNode.layout.position[0] - originRootX + rootX;
|
y = rootY - treeNode.layout.position[1];
|
}
|
else if (orient === 'vertical') {
|
x = treeNode.layout.position[0] - originRootX + rootX;
|
y = treeNode.layout.position[1] + rootY;
|
}
|
else if (orient === 'horizontal' && serie.direction === 'inverse') {
|
y = treeNode.layout.position[0] - originRootX + rootY;
|
x = rootX - treeNode.layout.position[1];
|
}
|
else if (orient === 'horizontal') {
|
y = treeNode.layout.position[0] - originRootX + rootY;
|
x = treeNode.layout.position[1] + rootX;
|
}
|
// 极坐标
|
else {
|
x = treeNode.layout.position[0];
|
y = treeNode.layout.position[1];
|
// 记录原始坐标,以后计算贝塞尔曲线的控制点
|
treeNode.layout.originPosition = [x, y];
|
var r = y;
|
var angle = (x - minX) / this.width * Math.PI * 2;
|
x = r * Math.cos(angle) + rootX;
|
y = r * Math.sin(angle) + rootY;
|
treeNode.layout.angle = angle;
|
}
|
treeNode.layout.position[0] = x;
|
treeNode.layout.position[1] = y;
|
},
|
this
|
);
|
},
|
/*
|
* 刷新
|
*/
|
/* refresh: function (newOption) {
|
this.clear();
|
if (newOption) {
|
this.option = newOption;
|
this.series = newOption.series;
|
}
|
|
this._buildShape();
|
}*/
|
refresh: function (newOption) {
|
this.clear();
|
|
if (newOption) {
|
this.option = newOption;
|
this.series = this.option.series;
|
}
|
|
// Map storing all trees of series
|
|
var series = this.series;
|
var legend = this.component.legend;
|
|
for (var i = 0; i < series.length; i++) {
|
if (series[i].type === ecConfig.CHART_TYPE_TREE) {
|
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('root', series.data);
|
|
this._treesMap[seriesIndex] = tree;*/
|
|
// this._buildTreemap(tree.root, seriesIndex);
|
this._buildShape(series, seriesIndex);
|
}
|
};
|
|
zrUtil.inherits(Tree, ChartBase);
|
|
// 图表注册
|
require('../chart').define('tree', Tree);
|
|
return Tree;
|
});
|