define('echarts/chart/pie', [ 'require', './base', 'zrender/shape/Text', 'zrender/shape/Ring', 'zrender/shape/Circle', 'zrender/shape/Sector', 'zrender/shape/Polyline', '../config', '../util/ecData', 'zrender/tool/util', 'zrender/tool/math', 'zrender/tool/color', '../chart' ], function (require) { var ChartBase = require('./base'); var TextShape = require('zrender/shape/Text'); var RingShape = require('zrender/shape/Ring'); var CircleShape = require('zrender/shape/Circle'); var SectorShape = require('zrender/shape/Sector'); var PolylineShape = require('zrender/shape/Polyline'); var ecConfig = require('../config'); ecConfig.pie = { zlevel: 0, z: 2, clickable: true, legendHoverLink: true, center: [ '50%', '50%' ], radius: [ 0, '75%' ], clockWise: true, startAngle: 90, minAngle: 0, selectedOffset: 10, itemStyle: { normal: { borderColor: 'rgba(0,0,0,0)', borderWidth: 1, label: { show: true, position: 'outer' }, labelLine: { show: true, length: 20, lineStyle: { width: 1, type: 'solid' } } }, emphasis: { borderColor: 'rgba(0,0,0,0)', borderWidth: 1, label: { show: false }, labelLine: { show: false, length: 20, lineStyle: { width: 1, type: 'solid' } } } } }; var ecData = require('../util/ecData'); var zrUtil = require('zrender/tool/util'); var zrMath = require('zrender/tool/math'); var zrColor = require('zrender/tool/color'); function Pie(ecTheme, messageCenter, zr, option, myChart) { ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart); var self = this; self.shapeHandler.onmouseover = function (param) { var shape = param.target; var seriesIndex = ecData.get(shape, 'seriesIndex'); var dataIndex = ecData.get(shape, 'dataIndex'); var percent = ecData.get(shape, 'special'); var center = [ shape.style.x, shape.style.y ]; var startAngle = shape.style.startAngle; var endAngle = shape.style.endAngle; var midAngle = ((endAngle + startAngle) / 2 + 360) % 360; var defaultColor = shape.highlightStyle.color; var label = self.getLabel(seriesIndex, dataIndex, percent, center, midAngle, defaultColor, true); if (label) { self.zr.addHoverShape(label); } var labelLine = self.getLabelLine(seriesIndex, dataIndex, center, shape.style.r0, shape.style.r, midAngle, defaultColor, true); if (labelLine) { self.zr.addHoverShape(labelLine); } }; this.refresh(option); } Pie.prototype = { type: ecConfig.CHART_TYPE_PIE, _buildShape: function () { var series = this.series; var legend = this.component.legend; this.selectedMap = {}; this._selected = {}; var center; var radius; var pieCase; this._selectedMode = false; var serieName; for (var i = 0, l = series.length; i < l; i++) { if (series[i].type === ecConfig.CHART_TYPE_PIE) { series[i] = this.reformOption(series[i]); this.legendHoverLink = series[i].legendHoverLink || this.legendHoverLink; serieName = series[i].name || ''; this.selectedMap[serieName] = legend ? legend.isSelected(serieName) : true; if (!this.selectedMap[serieName]) { continue; } center = this.parseCenter(this.zr, series[i].center); radius = this.parseRadius(this.zr, series[i].radius); this._selectedMode = this._selectedMode || series[i].selectedMode; this._selected[i] = []; if (this.deepQuery([ series[i], this.option ], 'calculable')) { pieCase = { zlevel: series[i].zlevel, z: series[i].z, hoverable: false, style: { x: center[0], y: center[1], r0: radius[0] <= 10 ? 0 : radius[0] - 10, r: radius[1] + 10, brushType: 'stroke', lineWidth: 1, strokeColor: series[i].calculableHolderColor || this.ecTheme.calculableHolderColor || ecConfig.calculableHolderColor } }; ecData.pack(pieCase, series[i], i, undefined, -1); this.setCalculable(pieCase); pieCase = radius[0] <= 10 ? new CircleShape(pieCase) : new RingShape(pieCase); this.shapeList.push(pieCase); } this._buildSinglePie(i); this.buildMark(i); } } this.addShapeList(); }, _buildSinglePie: function (seriesIndex) { var series = this.series; var serie = series[seriesIndex]; var data = serie.data; var legend = this.component.legend; var itemName; var totalSelected = 0; var totalSelectedValue0 = 0; var totalValue = 0; var maxValue = Number.NEGATIVE_INFINITY; var singleShapeList = []; for (var i = 0, l = data.length; i < l; i++) { itemName = data[i].name; this.selectedMap[itemName] = legend ? legend.isSelected(itemName) : true; if (this.selectedMap[itemName] && !isNaN(data[i].value)) { if (+data[i].value !== 0) { totalSelected++; } else { totalSelectedValue0++; } totalValue += +data[i].value; maxValue = Math.max(maxValue, +data[i].value); } } if (totalValue === 0) { return; } var percent = 100; var clockWise = serie.clockWise; var startAngle = (serie.startAngle.toFixed(2) - 0 + 360) % 360; var endAngle; var minAngle = serie.minAngle || 0.01; var totalAngle = 360 - minAngle * totalSelected - 0.01 * totalSelectedValue0; var defaultColor; var roseType = serie.roseType; var center; var radius; var r0; var r1; for (var i = 0, l = data.length; i < l; i++) { itemName = data[i].name; if (!this.selectedMap[itemName] || isNaN(data[i].value)) { continue; } defaultColor = legend ? legend.getColor(itemName) : this.zr.getColor(i); percent = data[i].value / totalValue; if (roseType != 'area') { endAngle = clockWise ? startAngle - percent * totalAngle - (percent !== 0 ? minAngle : 0.01) : percent * totalAngle + startAngle + (percent !== 0 ? minAngle : 0.01); } else { endAngle = clockWise ? startAngle - 360 / l : 360 / l + startAngle; } endAngle = endAngle.toFixed(2) - 0; percent = (percent * 100).toFixed(2); center = this.parseCenter(this.zr, serie.center); radius = this.parseRadius(this.zr, serie.radius); r0 = +radius[0]; r1 = +radius[1]; if (roseType === 'radius') { r1 = data[i].value / maxValue * (r1 - r0) * 0.8 + (r1 - r0) * 0.2 + r0; } else if (roseType === 'area') { r1 = Math.sqrt(data[i].value / maxValue) * (r1 - r0) + r0; } if (clockWise) { var temp; temp = startAngle; startAngle = endAngle; endAngle = temp; } this._buildItem(singleShapeList, seriesIndex, i, percent, data[i].selected, center, r0, r1, startAngle, endAngle, defaultColor); if (!clockWise) { startAngle = endAngle; } } this._autoLabelLayout(singleShapeList, center, r1); for (var i = 0, l = singleShapeList.length; i < l; i++) { this.shapeList.push(singleShapeList[i]); } singleShapeList = null; }, _buildItem: function (singleShapeList, seriesIndex, dataIndex, percent, isSelected, center, r0, r1, startAngle, endAngle, defaultColor) { var series = this.series; var midAngle = ((endAngle + startAngle) / 2 + 360) % 360; var sector = this.getSector(seriesIndex, dataIndex, percent, isSelected, center, r0, r1, startAngle, endAngle, defaultColor); ecData.pack(sector, series[seriesIndex], seriesIndex, series[seriesIndex].data[dataIndex], dataIndex, series[seriesIndex].data[dataIndex].name, percent); singleShapeList.push(sector); var label = this.getLabel(seriesIndex, dataIndex, percent, center, midAngle, defaultColor, false); var labelLine = this.getLabelLine(seriesIndex, dataIndex, center, r0, r1, midAngle, defaultColor, false); if (labelLine) { ecData.pack(labelLine, series[seriesIndex], seriesIndex, series[seriesIndex].data[dataIndex], dataIndex, series[seriesIndex].data[dataIndex].name, percent); singleShapeList.push(labelLine); } if (label) { ecData.pack(label, series[seriesIndex], seriesIndex, series[seriesIndex].data[dataIndex], dataIndex, series[seriesIndex].data[dataIndex].name, percent); label._labelLine = labelLine; singleShapeList.push(label); } }, getSector: function (seriesIndex, dataIndex, percent, isSelected, center, r0, r1, startAngle, endAngle, defaultColor) { var series = this.series; var serie = series[seriesIndex]; var data = serie.data[dataIndex]; var queryTarget = [ data, serie ]; var normal = this.deepMerge(queryTarget, 'itemStyle.normal') || {}; var emphasis = this.deepMerge(queryTarget, 'itemStyle.emphasis') || {}; var normalColor = this.getItemStyleColor(normal.color, seriesIndex, dataIndex, data) || defaultColor; var emphasisColor = this.getItemStyleColor(emphasis.color, seriesIndex, dataIndex, data) || (typeof normalColor === 'string' ? zrColor.lift(normalColor, -0.2) : normalColor); var sector = { zlevel: serie.zlevel, z: serie.z, clickable: this.deepQuery(queryTarget, 'clickable'), style: { x: center[0], y: center[1], r0: r0, r: r1, startAngle: startAngle, endAngle: endAngle, brushType: 'both', color: normalColor, lineWidth: normal.borderWidth, strokeColor: normal.borderColor, lineJoin: 'round' }, highlightStyle: { color: emphasisColor, lineWidth: emphasis.borderWidth, strokeColor: emphasis.borderColor, lineJoin: 'round' }, _seriesIndex: seriesIndex, _dataIndex: dataIndex }; if (isSelected) { var midAngle = ((sector.style.startAngle + sector.style.endAngle) / 2).toFixed(2) - 0; sector.style._hasSelected = true; sector.style._x = sector.style.x; sector.style._y = sector.style.y; var offset = this.query(serie, 'selectedOffset'); sector.style.x += zrMath.cos(midAngle, true) * offset; sector.style.y -= zrMath.sin(midAngle, true) * offset; this._selected[seriesIndex][dataIndex] = true; } else { this._selected[seriesIndex][dataIndex] = false; } if (this._selectedMode) { sector.onclick = this.shapeHandler.onclick; } if (this.deepQuery([ data, serie, this.option ], 'calculable')) { this.setCalculable(sector); sector.draggable = true; } if (this._needLabel(serie, data, true) || this._needLabelLine(serie, data, true)) { sector.onmouseover = this.shapeHandler.onmouseover; } sector = new SectorShape(sector); return sector; }, getLabel: function (seriesIndex, dataIndex, percent, center, midAngle, defaultColor, isEmphasis) { var series = this.series; var serie = series[seriesIndex]; var data = serie.data[dataIndex]; if (!this._needLabel(serie, data, isEmphasis)) { return; } var status = isEmphasis ? 'emphasis' : 'normal'; var itemStyle = zrUtil.merge(zrUtil.clone(data.itemStyle) || {}, serie.itemStyle); var labelControl = itemStyle[status].label; var textStyle = labelControl.textStyle || {}; var centerX = center[0]; var centerY = center[1]; var x; var y; var radius = this.parseRadius(this.zr, serie.radius); var textAlign; var textBaseline = 'middle'; labelControl.position = labelControl.position || itemStyle.normal.label.position; if (labelControl.position === 'center') { x = centerX; y = centerY; textAlign = 'center'; } else if (labelControl.position === 'inner' || labelControl.position === 'inside') { radius = (radius[0] + radius[1]) * (labelControl.distance || 0.5); x = Math.round(centerX + radius * zrMath.cos(midAngle, true)); y = Math.round(centerY - radius * zrMath.sin(midAngle, true)); defaultColor = '#fff'; textAlign = 'center'; } else { radius = radius[1] - -itemStyle[status].labelLine.length; x = Math.round(centerX + radius * zrMath.cos(midAngle, true)); y = Math.round(centerY - radius * zrMath.sin(midAngle, true)); textAlign = midAngle >= 90 && midAngle <= 270 ? 'right' : 'left'; } if (labelControl.position != 'center' && labelControl.position != 'inner' && labelControl.position != 'inside') { x += textAlign === 'left' ? 20 : -20; } data.__labelX = x - (textAlign === 'left' ? 5 : -5); data.__labelY = y; var ts = new TextShape({ zlevel: serie.zlevel, z: serie.z + 1, hoverable: false, style: { x: x, y: y, color: textStyle.color || defaultColor, text: this.getLabelText(seriesIndex, dataIndex, percent, status), textAlign: textStyle.align || textAlign, textBaseline: textStyle.baseline || textBaseline, textFont: this.getFont(textStyle) }, highlightStyle: { brushType: 'fill' } }); ts._radius = radius; ts._labelPosition = labelControl.position || 'outer'; ts._rect = ts.getRect(ts.style); ts._seriesIndex = seriesIndex; ts._dataIndex = dataIndex; return ts; }, getLabelText: function (seriesIndex, dataIndex, percent, status) { var series = this.series; var serie = series[seriesIndex]; var data = serie.data[dataIndex]; var formatter = this.deepQuery([ data, serie ], 'itemStyle.' + status + '.label.formatter'); if (formatter) { if (typeof formatter === 'function') { return formatter.call(this.myChart, { seriesIndex: seriesIndex, seriesName: serie.name || '', series: serie, dataIndex: dataIndex, data: data, name: data.name, value: data.value, percent: percent }); } else if (typeof formatter === 'string') { formatter = formatter.replace('{a}', '{a0}').replace('{b}', '{b0}').replace('{c}', '{c0}').replace('{d}', '{d0}'); formatter = formatter.replace('{a0}', serie.name).replace('{b0}', data.name).replace('{c0}', data.value).replace('{d0}', percent); return formatter; } } else { return data.name; } }, getLabelLine: function (seriesIndex, dataIndex, center, r0, r1, midAngle, defaultColor, isEmphasis) { var series = this.series; var serie = series[seriesIndex]; var data = serie.data[dataIndex]; if (this._needLabelLine(serie, data, isEmphasis)) { var status = isEmphasis ? 'emphasis' : 'normal'; var itemStyle = zrUtil.merge(zrUtil.clone(data.itemStyle) || {}, serie.itemStyle); var labelLineControl = itemStyle[status].labelLine; var lineStyle = labelLineControl.lineStyle || {}; var centerX = center[0]; var centerY = center[1]; var minRadius = r1; var maxRadius = this.parseRadius(this.zr, serie.radius)[1] - -labelLineControl.length; var cosValue = zrMath.cos(midAngle, true); var sinValue = zrMath.sin(midAngle, true); return new PolylineShape({ zlevel: serie.zlevel, z: serie.z + 1, hoverable: false, style: { pointList: [ [ centerX + minRadius * cosValue, centerY - minRadius * sinValue ], [ centerX + maxRadius * cosValue, centerY - maxRadius * sinValue ], [ data.__labelX, data.__labelY ] ], strokeColor: lineStyle.color || defaultColor, lineType: lineStyle.type, lineWidth: lineStyle.width }, _seriesIndex: seriesIndex, _dataIndex: dataIndex }); } else { return; } }, _needLabel: function (serie, data, isEmphasis) { return this.deepQuery([ data, serie ], 'itemStyle.' + (isEmphasis ? 'emphasis' : 'normal') + '.label.show'); }, _needLabelLine: function (serie, data, isEmphasis) { return this.deepQuery([ data, serie ], 'itemStyle.' + (isEmphasis ? 'emphasis' : 'normal') + '.labelLine.show'); }, _autoLabelLayout: function (sList, center, r) { var leftList = []; var rightList = []; for (var i = 0, l = sList.length; i < l; i++) { if (sList[i]._labelPosition === 'outer' || sList[i]._labelPosition === 'outside') { sList[i]._rect._y = sList[i]._rect.y; if (sList[i]._rect.x < center[0]) { leftList.push(sList[i]); } else { rightList.push(sList[i]); } } } this._layoutCalculate(leftList, center, r, -1); this._layoutCalculate(rightList, center, r, 1); }, _layoutCalculate: function (tList, center, r, direction) { tList.sort(function (a, b) { return a._rect.y - b._rect.y; }); function _changeDown(start, end, delta, direction) { for (var j = start; j < end; j++) { tList[j]._rect.y += delta; tList[j].style.y += delta; if (tList[j]._labelLine) { tList[j]._labelLine.style.pointList[1][1] += delta; tList[j]._labelLine.style.pointList[2][1] += delta; } if (j > start && j + 1 < end && tList[j + 1]._rect.y > tList[j]._rect.y + tList[j]._rect.height) { _changeUp(j, delta / 2); return; } } _changeUp(end - 1, delta / 2); } function _changeUp(end, delta) { for (var j = end; j >= 0; j--) { tList[j]._rect.y -= delta; tList[j].style.y -= delta; if (tList[j]._labelLine) { tList[j]._labelLine.style.pointList[1][1] -= delta; tList[j]._labelLine.style.pointList[2][1] -= delta; } if (j > 0 && tList[j]._rect.y > tList[j - 1]._rect.y + tList[j - 1]._rect.height) { break; } } } function _changeX(sList, isDownList, center, r, direction) { var x = center[0]; var y = center[1]; var deltaX; var deltaY; var length; var lastDeltaX = direction > 0 ? isDownList ? Number.MAX_VALUE : 0 : isDownList ? Number.MAX_VALUE : 0; for (var i = 0, l = sList.length; i < l; i++) { deltaY = Math.abs(sList[i]._rect.y - y); length = sList[i]._radius - r; deltaX = deltaY < r + length ? Math.sqrt((r + length + 20) * (r + length + 20) - Math.pow(sList[i]._rect.y - y, 2)) : Math.abs(sList[i]._rect.x + (direction > 0 ? 0 : sList[i]._rect.width) - x); if (isDownList && deltaX >= lastDeltaX) { deltaX = lastDeltaX - 10; } if (!isDownList && deltaX <= lastDeltaX) { deltaX = lastDeltaX + 10; } sList[i]._rect.x = sList[i].style.x = x + deltaX * direction; if (sList[i]._labelLine) { sList[i]._labelLine.style.pointList[2][0] = x + (deltaX - 5) * direction; sList[i]._labelLine.style.pointList[1][0] = x + (deltaX - 20) * direction; } lastDeltaX = deltaX; } } var lastY = 0; var delta; var len = tList.length; var upList = []; var downList = []; for (var i = 0; i < len; i++) { delta = tList[i]._rect.y - lastY; if (delta < 0) { _changeDown(i, len, -delta, direction); } lastY = tList[i]._rect.y + tList[i]._rect.height; } if (this.zr.getHeight() - lastY < 0) { _changeUp(len - 1, lastY - this.zr.getHeight()); } for (var i = 0; i < len; i++) { if (tList[i]._rect.y >= center[1]) { downList.push(tList[i]); } else { upList.push(tList[i]); } } _changeX(downList, true, center, r, direction); _changeX(upList, false, center, r, direction); }, reformOption: function (opt) { var _merge = zrUtil.merge; opt = _merge(_merge(opt || {}, zrUtil.clone(this.ecTheme.pie || {})), zrUtil.clone(ecConfig.pie)); opt.itemStyle.normal.label.textStyle = this.getTextStyle(opt.itemStyle.normal.label.textStyle); opt.itemStyle.emphasis.label.textStyle = this.getTextStyle(opt.itemStyle.emphasis.label.textStyle); this.z = opt.z; this.zlevel = opt.zlevel; return opt; }, refresh: function (newOption) { if (newOption) { this.option = newOption; this.series = newOption.series; } this.backupShapeList(); this._buildShape(); }, addDataAnimation: function (params, done) { var series = this.series; var aniMap = {}; for (var i = 0, l = params.length; i < l; i++) { aniMap[params[i][0]] = params[i]; } var aniCount = 0; function animationDone() { aniCount--; if (aniCount === 0) { done && done(); } } var sectorMap = {}; var textMap = {}; var lineMap = {}; var backupShapeList = this.shapeList; this.shapeList = []; var seriesIndex; var isHead; var dataGrow; var deltaIdxMap = {}; for (var i = 0, l = params.length; i < l; i++) { seriesIndex = params[i][0]; isHead = params[i][2]; dataGrow = params[i][3]; if (series[seriesIndex] && series[seriesIndex].type === ecConfig.CHART_TYPE_PIE) { if (isHead) { if (!dataGrow) { sectorMap[seriesIndex + '_' + series[seriesIndex].data.length] = 'delete'; } deltaIdxMap[seriesIndex] = 1; } else { if (!dataGrow) { sectorMap[seriesIndex + '_-1'] = 'delete'; deltaIdxMap[seriesIndex] = -1; } else { deltaIdxMap[seriesIndex] = 0; } } this._buildSinglePie(seriesIndex); } } var dataIndex; var key; for (var i = 0, l = this.shapeList.length; i < l; i++) { seriesIndex = this.shapeList[i]._seriesIndex; dataIndex = this.shapeList[i]._dataIndex; key = seriesIndex + '_' + dataIndex; switch (this.shapeList[i].type) { case 'sector': sectorMap[key] = this.shapeList[i]; break; case 'text': textMap[key] = this.shapeList[i]; break; case 'polyline': lineMap[key] = this.shapeList[i]; break; } } this.shapeList = []; var targeSector; for (var i = 0, l = backupShapeList.length; i < l; i++) { seriesIndex = backupShapeList[i]._seriesIndex; if (aniMap[seriesIndex]) { dataIndex = backupShapeList[i]._dataIndex + deltaIdxMap[seriesIndex]; key = seriesIndex + '_' + dataIndex; targeSector = sectorMap[key]; if (!targeSector) { continue; } if (backupShapeList[i].type === 'sector') { if (targeSector != 'delete') { aniCount++; this.zr.animate(backupShapeList[i].id, 'style').when(400, { startAngle: targeSector.style.startAngle, endAngle: targeSector.style.endAngle }).done(animationDone).start(); } else { aniCount++; this.zr.animate(backupShapeList[i].id, 'style').when(400, deltaIdxMap[seriesIndex] < 0 ? { startAngle: backupShapeList[i].style.startAngle } : { endAngle: backupShapeList[i].style.endAngle }).done(animationDone).start(); } } else if (backupShapeList[i].type === 'text' || backupShapeList[i].type === 'polyline') { if (targeSector === 'delete') { this.zr.delShape(backupShapeList[i].id); } else { switch (backupShapeList[i].type) { case 'text': aniCount++; targeSector = textMap[key]; this.zr.animate(backupShapeList[i].id, 'style').when(400, { x: targeSector.style.x, y: targeSector.style.y }).done(animationDone).start(); break; case 'polyline': aniCount++; targeSector = lineMap[key]; this.zr.animate(backupShapeList[i].id, 'style').when(400, { pointList: targeSector.style.pointList }).done(animationDone).start(); break; } } } } } this.shapeList = backupShapeList; if (!aniCount) { done && done(); } }, onclick: function (param) { var series = this.series; if (!this.isClick || !param.target) { return; } this.isClick = false; var offset; var target = param.target; var style = target.style; var seriesIndex = ecData.get(target, 'seriesIndex'); var dataIndex = ecData.get(target, 'dataIndex'); for (var i = 0, len = this.shapeList.length; i < len; i++) { if (this.shapeList[i].id === target.id) { seriesIndex = ecData.get(target, 'seriesIndex'); dataIndex = ecData.get(target, 'dataIndex'); if (!style._hasSelected) { var midAngle = ((style.startAngle + style.endAngle) / 2).toFixed(2) - 0; target.style._hasSelected = true; this._selected[seriesIndex][dataIndex] = true; target.style._x = target.style.x; target.style._y = target.style.y; offset = this.query(series[seriesIndex], 'selectedOffset'); target.style.x += zrMath.cos(midAngle, true) * offset; target.style.y -= zrMath.sin(midAngle, true) * offset; } else { target.style.x = target.style._x; target.style.y = target.style._y; target.style._hasSelected = false; this._selected[seriesIndex][dataIndex] = false; } this.zr.modShape(target.id); } else if (this.shapeList[i].style._hasSelected && this._selectedMode === 'single') { seriesIndex = ecData.get(this.shapeList[i], 'seriesIndex'); dataIndex = ecData.get(this.shapeList[i], 'dataIndex'); this.shapeList[i].style.x = this.shapeList[i].style._x; this.shapeList[i].style.y = this.shapeList[i].style._y; this.shapeList[i].style._hasSelected = false; this._selected[seriesIndex][dataIndex] = false; this.zr.modShape(this.shapeList[i].id); } } this.messageCenter.dispatch(ecConfig.EVENT.PIE_SELECTED, param.event, { selected: this._selected, target: ecData.get(target, 'name') }, this.myChart); this.zr.refreshNextFrame(); } }; zrUtil.inherits(Pie, ChartBase); require('../chart').define('pie', Pie); return Pie; });