| define('echarts/chart/funnel', [ |
| 'require', |
| './base', |
| 'zrender/shape/Text', |
| 'zrender/shape/Line', |
| 'zrender/shape/Polygon', |
| '../config', |
| '../util/ecData', |
| '../util/number', |
| 'zrender/tool/util', |
| 'zrender/tool/color', |
| 'zrender/tool/area', |
| '../chart' |
| ], function (require) { |
| var ChartBase = require('./base'); |
| var TextShape = require('zrender/shape/Text'); |
| var LineShape = require('zrender/shape/Line'); |
| var PolygonShape = require('zrender/shape/Polygon'); |
| var ecConfig = require('../config'); |
| ecConfig.funnel = { |
| zlevel: 0, |
| z: 2, |
| clickable: true, |
| legendHoverLink: true, |
| x: 80, |
| y: 60, |
| x2: 80, |
| y2: 60, |
| min: 0, |
| max: 100, |
| minSize: '0%', |
| maxSize: '100%', |
| sort: 'descending', |
| gap: 0, |
| funnelAlign: 'center', |
| itemStyle: { |
| normal: { |
| borderColor: '#fff', |
| borderWidth: 1, |
| label: { |
| show: true, |
| position: 'outer' |
| }, |
| labelLine: { |
| show: true, |
| length: 10, |
| lineStyle: { |
| width: 1, |
| type: 'solid' |
| } |
| } |
| }, |
| emphasis: { |
| borderColor: 'rgba(0,0,0,0)', |
| borderWidth: 1, |
| label: { show: true }, |
| labelLine: { show: true } |
| } |
| } |
| }; |
| var ecData = require('../util/ecData'); |
| var number = require('../util/number'); |
| var zrUtil = require('zrender/tool/util'); |
| var zrColor = require('zrender/tool/color'); |
| var zrArea = require('zrender/tool/area'); |
| function Funnel(ecTheme, messageCenter, zr, option, myChart) { |
| ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart); |
| this.refresh(option); |
| } |
| Funnel.prototype = { |
| type: ecConfig.CHART_TYPE_FUNNEL, |
| _buildShape: function () { |
| var series = this.series; |
| var legend = this.component.legend; |
| this._paramsMap = {}; |
| this._selected = {}; |
| this.selectedMap = {}; |
| var serieName; |
| for (var i = 0, l = series.length; i < l; i++) { |
| if (series[i].type === ecConfig.CHART_TYPE_FUNNEL) { |
| 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; |
| } |
| this._buildSingleFunnel(i); |
| this.buildMark(i); |
| } |
| } |
| this.addShapeList(); |
| }, |
| _buildSingleFunnel: function (seriesIndex) { |
| var legend = this.component.legend; |
| var serie = this.series[seriesIndex]; |
| var data = this._mapData(seriesIndex); |
| var location = this._getLocation(seriesIndex); |
| this._paramsMap[seriesIndex] = { |
| location: location, |
| data: data |
| }; |
| var itemName; |
| var total = 0; |
| var selectedData = []; |
| 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)) { |
| selectedData.push(data[i]); |
| total++; |
| } |
| } |
| if (total === 0) { |
| return; |
| } |
| var funnelCase = this._buildFunnelCase(seriesIndex); |
| var align = serie.funnelAlign; |
| var gap = serie.gap; |
| var height = total > 1 ? (location.height - (total - 1) * gap) / total : location.height; |
| var width; |
| var lastY = location.y; |
| var lastWidth = serie.sort === 'descending' ? this._getItemWidth(seriesIndex, selectedData[0].value) : number.parsePercent(serie.minSize, location.width); |
| var next = serie.sort === 'descending' ? 1 : 0; |
| var centerX = location.centerX; |
| var pointList = []; |
| var x; |
| var polygon; |
| var lastPolygon; |
| for (var i = 0, l = selectedData.length; i < l; i++) { |
| itemName = selectedData[i].name; |
| if (this.selectedMap[itemName] && !isNaN(selectedData[i].value)) { |
| width = i <= l - 2 ? this._getItemWidth(seriesIndex, selectedData[i + next].value) : serie.sort === 'descending' ? number.parsePercent(serie.minSize, location.width) : number.parsePercent(serie.maxSize, location.width); |
| switch (align) { |
| case 'left': |
| x = location.x; |
| break; |
| case 'right': |
| x = location.x + location.width - lastWidth; |
| break; |
| default: |
| x = centerX - lastWidth / 2; |
| } |
| polygon = this._buildItem(seriesIndex, selectedData[i]._index, legend ? legend.getColor(itemName) : this.zr.getColor(selectedData[i]._index), x, lastY, lastWidth, width, height, align); |
| lastY += height + gap; |
| lastPolygon = polygon.style.pointList; |
| pointList.unshift([ |
| lastPolygon[0][0] - 10, |
| lastPolygon[0][1] |
| ]); |
| pointList.push([ |
| lastPolygon[1][0] + 10, |
| lastPolygon[1][1] |
| ]); |
| if (i === 0) { |
| if (lastWidth === 0) { |
| lastPolygon = pointList.pop(); |
| align == 'center' && (pointList[0][0] += 10); |
| align == 'right' && (pointList[0][0] = lastPolygon[0]); |
| pointList[0][1] -= align == 'center' ? 10 : 15; |
| if (l == 1) { |
| lastPolygon = polygon.style.pointList; |
| } |
| } else { |
| pointList[pointList.length - 1][1] -= 5; |
| pointList[0][1] -= 5; |
| } |
| } |
| lastWidth = width; |
| } |
| } |
| if (funnelCase) { |
| pointList.unshift([ |
| lastPolygon[3][0] - 10, |
| lastPolygon[3][1] |
| ]); |
| pointList.push([ |
| lastPolygon[2][0] + 10, |
| lastPolygon[2][1] |
| ]); |
| if (lastWidth === 0) { |
| lastPolygon = pointList.pop(); |
| align == 'center' && (pointList[0][0] += 10); |
| align == 'right' && (pointList[0][0] = lastPolygon[0]); |
| pointList[0][1] += align == 'center' ? 10 : 15; |
| } else { |
| pointList[pointList.length - 1][1] += 5; |
| pointList[0][1] += 5; |
| } |
| funnelCase.style.pointList = pointList; |
| } |
| }, |
| _buildFunnelCase: function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| if (this.deepQuery([ |
| serie, |
| this.option |
| ], 'calculable')) { |
| var location = this._paramsMap[seriesIndex].location; |
| var gap = 10; |
| var funnelCase = { |
| hoverable: false, |
| style: { |
| pointListd: [ |
| [ |
| location.x - gap, |
| location.y - gap |
| ], |
| [ |
| location.x + location.width + gap, |
| location.y - gap |
| ], |
| [ |
| location.x + location.width + gap, |
| location.y + location.height + gap |
| ], |
| [ |
| location.x - gap, |
| location.y + location.height + gap |
| ] |
| ], |
| brushType: 'stroke', |
| lineWidth: 1, |
| strokeColor: serie.calculableHolderColor || this.ecTheme.calculableHolderColor || ecConfig.calculableHolderColor |
| } |
| }; |
| ecData.pack(funnelCase, serie, seriesIndex, undefined, -1); |
| this.setCalculable(funnelCase); |
| funnelCase = new PolygonShape(funnelCase); |
| this.shapeList.push(funnelCase); |
| return funnelCase; |
| } |
| }, |
| _getLocation: function (seriesIndex) { |
| var gridOption = this.series[seriesIndex]; |
| var zrWidth = this.zr.getWidth(); |
| var zrHeight = this.zr.getHeight(); |
| var x = this.parsePercent(gridOption.x, zrWidth); |
| var y = this.parsePercent(gridOption.y, zrHeight); |
| var width = gridOption.width == null ? zrWidth - x - this.parsePercent(gridOption.x2, zrWidth) : this.parsePercent(gridOption.width, zrWidth); |
| return { |
| x: x, |
| y: y, |
| width: width, |
| height: gridOption.height == null ? zrHeight - y - this.parsePercent(gridOption.y2, zrHeight) : this.parsePercent(gridOption.height, zrHeight), |
| centerX: x + width / 2 |
| }; |
| }, |
| _mapData: function (seriesIndex) { |
| var serie = this.series[seriesIndex]; |
| var funnelData = zrUtil.clone(serie.data); |
| for (var i = 0, l = funnelData.length; i < l; i++) { |
| funnelData[i]._index = i; |
| } |
| function numDescending(a, b) { |
| if (a.value === '-') { |
| return 1; |
| } else if (b.value === '-') { |
| return -1; |
| } |
| return b.value - a.value; |
| } |
| function numAscending(a, b) { |
| return -numDescending(a, b); |
| } |
| if (serie.sort != 'none') { |
| funnelData.sort(serie.sort === 'descending' ? numDescending : numAscending); |
| } |
| return funnelData; |
| }, |
| _buildItem: function (seriesIndex, dataIndex, defaultColor, x, y, topWidth, bottomWidth, height, align) { |
| var series = this.series; |
| var serie = series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| var polygon = this.getPolygon(seriesIndex, dataIndex, defaultColor, x, y, topWidth, bottomWidth, height, align); |
| ecData.pack(polygon, series[seriesIndex], seriesIndex, series[seriesIndex].data[dataIndex], dataIndex, series[seriesIndex].data[dataIndex].name); |
| this.shapeList.push(polygon); |
| var label = this.getLabel(seriesIndex, dataIndex, defaultColor, x, y, topWidth, bottomWidth, height, align); |
| ecData.pack(label, series[seriesIndex], seriesIndex, series[seriesIndex].data[dataIndex], dataIndex, series[seriesIndex].data[dataIndex].name); |
| this.shapeList.push(label); |
| if (!this._needLabel(serie, data, false)) { |
| label.invisible = true; |
| } |
| var labelLine = this.getLabelLine(seriesIndex, dataIndex, defaultColor, x, y, topWidth, bottomWidth, height, align); |
| this.shapeList.push(labelLine); |
| if (!this._needLabelLine(serie, data, false)) { |
| labelLine.invisible = true; |
| } |
| var polygonHoverConnect = []; |
| var labelHoverConnect = []; |
| if (this._needLabelLine(serie, data, true)) { |
| polygonHoverConnect.push(labelLine.id); |
| labelHoverConnect.push(labelLine.id); |
| } |
| if (this._needLabel(serie, data, true)) { |
| polygonHoverConnect.push(label.id); |
| labelHoverConnect.push(polygon.id); |
| } |
| polygon.hoverConnect = polygonHoverConnect; |
| label.hoverConnect = labelHoverConnect; |
| return polygon; |
| }, |
| _getItemWidth: function (seriesIndex, value) { |
| var serie = this.series[seriesIndex]; |
| var location = this._paramsMap[seriesIndex].location; |
| var min = serie.min; |
| var max = serie.max; |
| var minSize = number.parsePercent(serie.minSize, location.width); |
| var maxSize = number.parsePercent(serie.maxSize, location.width); |
| return value * (maxSize - minSize) / (max - min); |
| }, |
| getPolygon: function (seriesIndex, dataIndex, defaultColor, xLT, y, topWidth, bottomWidth, height, align) { |
| var serie = this.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 xLB; |
| switch (align) { |
| case 'left': |
| xLB = xLT; |
| break; |
| case 'right': |
| xLB = xLT + (topWidth - bottomWidth); |
| break; |
| default: |
| xLB = xLT + (topWidth - bottomWidth) / 2; |
| break; |
| } |
| var polygon = { |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase(), |
| clickable: this.deepQuery(queryTarget, 'clickable'), |
| style: { |
| pointList: [ |
| [ |
| xLT, |
| y |
| ], |
| [ |
| xLT + topWidth, |
| y |
| ], |
| [ |
| xLB + bottomWidth, |
| y + height |
| ], |
| [ |
| xLB, |
| y + height |
| ] |
| ], |
| brushType: 'both', |
| color: normalColor, |
| lineWidth: normal.borderWidth, |
| strokeColor: normal.borderColor |
| }, |
| highlightStyle: { |
| color: emphasisColor, |
| lineWidth: emphasis.borderWidth, |
| strokeColor: emphasis.borderColor |
| } |
| }; |
| if (this.deepQuery([ |
| data, |
| serie, |
| this.option |
| ], 'calculable')) { |
| this.setCalculable(polygon); |
| polygon.draggable = true; |
| } |
| return new PolygonShape(polygon); |
| }, |
| getLabel: function (seriesIndex, dataIndex, defaultColor, x, y, topWidth, bottomWidth, height, align) { |
| var serie = this.series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| var location = this._paramsMap[seriesIndex].location; |
| var itemStyle = zrUtil.merge(zrUtil.clone(data.itemStyle) || {}, serie.itemStyle); |
| var status = 'normal'; |
| var labelControl = itemStyle[status].label; |
| var textStyle = labelControl.textStyle || {}; |
| var lineLength = itemStyle[status].labelLine.length; |
| var text = this.getLabelText(seriesIndex, dataIndex, status); |
| var textFont = this.getFont(textStyle); |
| var textAlign; |
| var textColor = defaultColor; |
| labelControl.position = labelControl.position || itemStyle.normal.label.position; |
| if (labelControl.position === 'inner' || labelControl.position === 'inside' || labelControl.position === 'center') { |
| textAlign = align; |
| textColor = Math.max(topWidth, bottomWidth) / 2 > zrArea.getTextWidth(text, textFont) ? '#fff' : zrColor.reverse(defaultColor); |
| } else if (labelControl.position === 'left') { |
| textAlign = 'right'; |
| } else { |
| textAlign = 'left'; |
| } |
| var textShape = { |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase() + 1, |
| style: { |
| x: this._getLabelPoint(labelControl.position, x, location, topWidth, bottomWidth, lineLength, align), |
| y: y + height / 2, |
| color: textStyle.color || textColor, |
| text: text, |
| textAlign: textStyle.align || textAlign, |
| textBaseline: textStyle.baseline || 'middle', |
| textFont: textFont |
| } |
| }; |
| status = 'emphasis'; |
| labelControl = itemStyle[status].label || labelControl; |
| textStyle = labelControl.textStyle || textStyle; |
| lineLength = itemStyle[status].labelLine.length || lineLength; |
| labelControl.position = labelControl.position || itemStyle.normal.label.position; |
| text = this.getLabelText(seriesIndex, dataIndex, status); |
| textFont = this.getFont(textStyle); |
| textColor = defaultColor; |
| if (labelControl.position === 'inner' || labelControl.position === 'inside' || labelControl.position === 'center') { |
| textAlign = align; |
| textColor = Math.max(topWidth, bottomWidth) / 2 > zrArea.getTextWidth(text, textFont) ? '#fff' : zrColor.reverse(defaultColor); |
| } else if (labelControl.position === 'left') { |
| textAlign = 'right'; |
| } else { |
| textAlign = 'left'; |
| } |
| textShape.highlightStyle = { |
| x: this._getLabelPoint(labelControl.position, x, location, topWidth, bottomWidth, lineLength, align), |
| color: textStyle.color || textColor, |
| text: text, |
| textAlign: textStyle.align || textAlign, |
| textFont: textFont, |
| brushType: 'fill' |
| }; |
| return new TextShape(textShape); |
| }, |
| getLabelText: function (seriesIndex, dataIndex, 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 |
| }); |
| } else if (typeof formatter === 'string') { |
| formatter = formatter.replace('{a}', '{a0}').replace('{b}', '{b0}').replace('{c}', '{c0}').replace('{a0}', serie.name).replace('{b0}', data.name).replace('{c0}', data.value); |
| return formatter; |
| } |
| } else { |
| return data.name; |
| } |
| }, |
| getLabelLine: function (seriesIndex, dataIndex, defaultColor, x, y, topWidth, bottomWidth, height, align) { |
| var serie = this.series[seriesIndex]; |
| var data = serie.data[dataIndex]; |
| var location = this._paramsMap[seriesIndex].location; |
| var itemStyle = zrUtil.merge(zrUtil.clone(data.itemStyle) || {}, serie.itemStyle); |
| var status = 'normal'; |
| var labelLineControl = itemStyle[status].labelLine; |
| var lineLength = itemStyle[status].labelLine.length; |
| var lineStyle = labelLineControl.lineStyle || {}; |
| var labelControl = itemStyle[status].label; |
| labelControl.position = labelControl.position || itemStyle.normal.label.position; |
| var lineShape = { |
| zlevel: this.getZlevelBase(), |
| z: this.getZBase() + 1, |
| hoverable: false, |
| style: { |
| xStart: this._getLabelLineStartPoint(x, location, topWidth, bottomWidth, align), |
| yStart: y + height / 2, |
| xEnd: this._getLabelPoint(labelControl.position, x, location, topWidth, bottomWidth, lineLength, align), |
| yEnd: y + height / 2, |
| strokeColor: lineStyle.color || defaultColor, |
| lineType: lineStyle.type, |
| lineWidth: lineStyle.width |
| } |
| }; |
| status = 'emphasis'; |
| labelLineControl = itemStyle[status].labelLine || labelLineControl; |
| lineLength = itemStyle[status].labelLine.length || lineLength; |
| lineStyle = labelLineControl.lineStyle || lineStyle; |
| labelControl = itemStyle[status].label || labelControl; |
| labelControl.position = labelControl.position; |
| lineShape.highlightStyle = { |
| xEnd: this._getLabelPoint(labelControl.position, x, location, topWidth, bottomWidth, lineLength, align), |
| strokeColor: lineStyle.color || defaultColor, |
| lineType: lineStyle.type, |
| lineWidth: lineStyle.width |
| }; |
| return new LineShape(lineShape); |
| }, |
| _getLabelPoint: function (position, x, location, topWidth, bottomWidth, lineLength, align) { |
| position = position === 'inner' || position === 'inside' ? 'center' : position; |
| switch (position) { |
| case 'center': |
| return align == 'center' ? x + topWidth / 2 : align == 'left' ? x + 10 : x + topWidth - 10; |
| case 'left': |
| if (lineLength === 'auto') { |
| return location.x - 10; |
| } else { |
| return align == 'center' ? location.centerX - Math.max(topWidth, bottomWidth) / 2 - lineLength : align == 'right' ? x - (topWidth < bottomWidth ? bottomWidth - topWidth : 0) - lineLength : location.x - lineLength; |
| } |
| break; |
| default: |
| if (lineLength === 'auto') { |
| return location.x + location.width + 10; |
| } else { |
| return align == 'center' ? location.centerX + Math.max(topWidth, bottomWidth) / 2 + lineLength : align == 'right' ? location.x + location.width + lineLength : x + Math.max(topWidth, bottomWidth) + lineLength; |
| } |
| } |
| }, |
| _getLabelLineStartPoint: function (x, location, topWidth, bottomWidth, align) { |
| return align == 'center' ? location.centerX : topWidth < bottomWidth ? x + Math.min(topWidth, bottomWidth) / 2 : x + Math.max(topWidth, bottomWidth) / 2; |
| }, |
| _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'); |
| }, |
| refresh: function (newOption) { |
| if (newOption) { |
| this.option = newOption; |
| this.series = newOption.series; |
| } |
| this.backupShapeList(); |
| this._buildShape(); |
| } |
| }; |
| zrUtil.inherits(Funnel, ChartBase); |
| require('../chart').define('funnel', Funnel); |
| return Funnel; |
| }); |