123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630 |
- /**
- * 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;
- });
|