/** * echarts图表类:散点图 * * @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。 * @author Kener (@Kener-林峰, kener.linfeng@gmail.com) * */ define(function (require) { var ChartBase = require('./base'); // 图形依赖 var SymbolShape = require('../util/shape/Symbol'); // 组件依赖 require('../component/axis'); require('../component/grid'); require('../component/dataZoom'); require('../component/dataRange'); var ecConfig = require('../config'); // 散点图默认参数 ecConfig.scatter = { zlevel: 0, // 一级层叠 z: 2, // 二级层叠 clickable: true, legendHoverLink: true, xAxisIndex: 0, yAxisIndex: 0, // symbol: null, // 图形类型 symbolSize: 4, // 图形大小,半宽(半径)参数,当图形为方向或菱形则总宽度为symbolSize * 2 // symbolRotate: null, // 图形旋转控制 large: false, // 大规模散点图 largeThreshold: 2000, // 大规模阀值,large为true且数据量>largeThreshold才启用大规模模式 itemStyle: { normal: { // color: 各异, label: { show: false // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调 // position: 默认自适应,水平布局为'top',垂直布局为'right',可选为 // 'inside'|'left'|'right'|'top'|'bottom' // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE } }, emphasis: { // color: '各异' label: { show: false // formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调 // position: 默认自适应,水平布局为'top',垂直布局为'right',可选为 // 'inside'|'left'|'right'|'top'|'bottom' // textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE } } } }; var zrUtil = require('zrender/tool/util'); var zrColor = require('zrender/tool/color'); /** * 构造函数 * @param {Object} messageCenter echart消息中心 * @param {ZRender} zr zrender实例 * @param {Object} series 数据 * @param {Object} component 组件 */ function Scatter(ecTheme, messageCenter, zr, option, myChart){ // 图表基类 ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart); this.refresh(option); } Scatter.prototype = { type: ecConfig.CHART_TYPE_SCATTER, /** * 绘制图形 */ _buildShape: function () { var series = this.series; this._sIndex2ColorMap = {}; // series默认颜色索引,seriesIndex索引到color this._symbol = this.option.symbolList; this._sIndex2ShapeMap = {}; // series图形类型,seriesIndex索引到_symbol this.selectedMap = {}; this.xMarkMap = {}; var legend = this.component.legend; var seriesArray = []; var serie; // 临时映射变量 var serieName; // 临时映射变量 var iconShape; var iconType; for (var i = 0, l = series.length; i < l; i++) { serie = series[i]; serieName = serie.name; if (serie.type === ecConfig.CHART_TYPE_SCATTER) { series[i] = this.reformOption(series[i]); this.legendHoverLink = series[i].legendHoverLink || this.legendHoverLink; this._sIndex2ShapeMap[i] = this.query(serie, 'symbol') || this._symbol[i % this._symbol.length]; if (legend){ this.selectedMap[serieName] = legend.isSelected(serieName); this._sIndex2ColorMap[i] = zrColor.alpha(legend.getColor(serieName), 0.5); iconShape = legend.getItemShape(serieName); if (iconShape) { // 回调legend,换一个更形象的icon var iconType = this._sIndex2ShapeMap[i]; iconShape.style.brushType = iconType.match('empty') ? 'stroke' : 'both'; iconType = iconType.replace('empty', '').toLowerCase(); if (iconType.match('rectangle')) { iconShape.style.x += Math.round( (iconShape.style.width - iconShape.style.height) / 2 ); iconShape.style.width = iconShape.style.height; } if (iconType.match('star')) { iconShape.style.n = (iconType.replace('star','') - 0) || 5; iconType = 'star'; } if (iconType.match('image')) { iconShape.style.image = iconType.replace( new RegExp('^image:\\/\\/'), '' ); iconShape.style.x += Math.round( (iconShape.style.width - iconShape.style.height) / 2 ); iconShape.style.width = iconShape.style.height; iconType = 'image'; } iconShape.style.iconType = iconType; legend.setItemShape(serieName, iconShape); } } else { this.selectedMap[serieName] = true; this._sIndex2ColorMap[i] = zrColor.alpha(this.zr.getColor(i), 0.5); } if (this.selectedMap[serieName]) { seriesArray.push(i); } } } this._buildSeries(seriesArray); this.addShapeList(); }, /** * 构建类目轴为水平方向的散点图系列 */ _buildSeries: function (seriesArray) { if (seriesArray.length === 0) { return; } var series = this.series; var seriesIndex; var serie; var data; var value; var xAxis; var yAxis; var pointList = {}; var x; var y; for (var j = 0, k = seriesArray.length; j < k; j++) { seriesIndex = seriesArray[j]; serie = series[seriesIndex]; if (serie.data.length === 0) { continue; } xAxis = this.component.xAxis.getAxis(serie.xAxisIndex || 0); yAxis = this.component.yAxis.getAxis(serie.yAxisIndex || 0); pointList[seriesIndex] = []; for (var i = 0, l = serie.data.length; i < l; i++) { data = serie.data[i]; value = this.getDataFromOption(data, '-'); if (value === '-' || value.length < 2) { // 数据格式不符 continue; } x = xAxis.getCoord(value[0]); y = yAxis.getCoord(value[1]); pointList[seriesIndex].push([ x, // 横坐标 y, // 纵坐标 i, // 数据index data.name || '' // 名称 ]); } this.xMarkMap[seriesIndex] = this._markMap( xAxis, yAxis, serie.data, pointList[seriesIndex] ); this.buildMark(seriesIndex); } // console.log(pointList) this._buildPointList(pointList); }, _markMap: function (xAxis, yAxis, data, pointList) { var xMarkMap = { min0: Number.POSITIVE_INFINITY, max0: Number.NEGATIVE_INFINITY, sum0: 0, counter0: 0, average0: 0, min1: Number.POSITIVE_INFINITY, max1: Number.NEGATIVE_INFINITY, sum1: 0, counter1: 0, average1: 0 }; var value; for (var i = 0, l = pointList.length; i < l; i++) { /** x, // 横坐标 y, // 纵坐标 i, // 数据index data.name || '' // 名称 */ value = data[pointList[i][2]].value || data[pointList[i][2]]; // 横轴 if (xMarkMap.min0 > value[0]) { xMarkMap.min0 = value[0]; xMarkMap.minY0 = pointList[i][1]; xMarkMap.minX0 = pointList[i][0]; } if (xMarkMap.max0 < value[0]) { xMarkMap.max0 = value[0]; xMarkMap.maxY0 = pointList[i][1]; xMarkMap.maxX0 = pointList[i][0]; } xMarkMap.sum0 += value[0]; xMarkMap.counter0++; // 纵轴 if (xMarkMap.min1 > value[1]) { xMarkMap.min1 = value[1]; xMarkMap.minY1 = pointList[i][1]; xMarkMap.minX1 = pointList[i][0]; } if (xMarkMap.max1 < value[1]) { xMarkMap.max1 = value[1]; xMarkMap.maxY1 = pointList[i][1]; xMarkMap.maxX1 = pointList[i][0]; } xMarkMap.sum1 += value[1]; xMarkMap.counter1++; } var gridX = this.component.grid.getX(); var gridXend = this.component.grid.getXend(); var gridY = this.component.grid.getY(); var gridYend = this.component.grid.getYend(); xMarkMap.average0 = xMarkMap.sum0 / xMarkMap.counter0; var x = xAxis.getCoord(xMarkMap.average0); // 横轴平均纵向 xMarkMap.averageLine0 = [ [x, gridYend], [x, gridY] ]; xMarkMap.minLine0 = [ [xMarkMap.minX0, gridYend], [xMarkMap.minX0, gridY] ]; xMarkMap.maxLine0 = [ [xMarkMap.maxX0, gridYend], [xMarkMap.maxX0, gridY] ]; xMarkMap.average1 = xMarkMap.sum1 / xMarkMap.counter1; var y = yAxis.getCoord(xMarkMap.average1); // 纵轴平均横向 xMarkMap.averageLine1 = [ [gridX, y], [gridXend, y] ]; xMarkMap.minLine1 = [ [gridX, xMarkMap.minY1], [gridXend, xMarkMap.minY1] ]; xMarkMap.maxLine1 = [ [gridX, xMarkMap.maxY1], [gridXend, xMarkMap.maxY1] ]; return xMarkMap; }, /** * 生成折线和折线上的拐点 */ _buildPointList: function (pointList) { var series = this.series; var serie; var seriesPL; var singlePoint; var shape; for (var seriesIndex in pointList) { serie = series[seriesIndex]; seriesPL = pointList[seriesIndex]; if (serie.large && serie.data.length > serie.largeThreshold) { this.shapeList.push(this._getLargeSymbol( serie, seriesPL, this.getItemStyleColor( this.query( serie, 'itemStyle.normal.color' ), seriesIndex, -1 ) || this._sIndex2ColorMap[seriesIndex] )); continue; } /* * pointlist=[ * 0 x, * 1 y, * 2 数据index * 3 名称 * ] */ for (var i = 0, l = seriesPL.length; i < l; i++) { singlePoint = seriesPL[i]; shape = this._getSymbol( seriesIndex, // seriesIndex singlePoint[2], // dataIndex singlePoint[3], // name singlePoint[0], // x singlePoint[1] // y ); shape && this.shapeList.push(shape); } } // console.log(this.shapeList) }, /** * 生成折线图上的拐点图形 */ _getSymbol: function (seriesIndex, dataIndex, name, x, y) { var series = this.series; var serie = series[seriesIndex]; var data = serie.data[dataIndex]; var dataRange = this.component.dataRange; var rangColor; if (dataRange) { rangColor = isNaN(data[2]) ? this._sIndex2ColorMap[seriesIndex] : dataRange.getColor(data[2]); if (!rangColor) { return null; } } else { rangColor = this._sIndex2ColorMap[seriesIndex]; } var itemShape = this.getSymbolShape( serie, seriesIndex, data, dataIndex, name, x, y, this._sIndex2ShapeMap[seriesIndex], rangColor, 'rgba(0,0,0,0)', 'vertical' ); itemShape.zlevel = serie.zlevel; itemShape.z = serie.z; itemShape._main = true; return itemShape; }, _getLargeSymbol: function (serie, pointList, nColor) { return new SymbolShape({ zlevel: serie.zlevel, z: serie.z, _main: true, hoverable: false, style: { pointList: pointList, color: nColor, strokeColor: nColor }, highlightStyle: { pointList: [] } }); }, // 位置转换 getMarkCoord: function (seriesIndex, mpData) { var serie = this.series[seriesIndex]; var xMarkMap = this.xMarkMap[seriesIndex]; var xAxis = this.component.xAxis.getAxis(serie.xAxisIndex); var yAxis = this.component.yAxis.getAxis(serie.yAxisIndex); var pos; if (mpData.type && (mpData.type === 'max' || mpData.type === 'min' || mpData.type === 'average') ) { // 特殊值内置支持 // 默认取纵值 var valueIndex = mpData.valueIndex != null ? mpData.valueIndex : 1; pos = [ xMarkMap[mpData.type + 'X' + valueIndex], xMarkMap[mpData.type + 'Y' + valueIndex], xMarkMap[mpData.type + 'Line' + valueIndex], xMarkMap[mpData.type + valueIndex] ]; } else { pos = [ typeof mpData.xAxis != 'string' && xAxis.getCoordByIndex ? xAxis.getCoordByIndex(mpData.xAxis || 0) : xAxis.getCoord(mpData.xAxis || 0), typeof mpData.yAxis != 'string' && yAxis.getCoordByIndex ? yAxis.getCoordByIndex(mpData.yAxis || 0) : yAxis.getCoord(mpData.yAxis || 0) ]; } return pos; }, /** * 刷新 */ refresh: function (newOption) { if (newOption) { this.option = newOption; this.series = newOption.series; } this.backupShapeList(); this._buildShape(); }, /** * 值域响应 * @param {Object} param * @param {Object} status */ ondataRange: function (param, status) { if (this.component.dataRange) { this.refresh(); status.needRefresh = true; } return; } }; zrUtil.inherits(Scatter, ChartBase); // 图表注册 require('../chart').define('scatter', Scatter); return Scatter; });