You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
557 lines
21 KiB
557 lines
21 KiB
/**
|
|
* echarts图表类:K线图
|
|
*
|
|
* @desc echarts基于Canvas,纯Javascript图表库,提供直观,生动,可交互,可个性化定制的数据统计图表。
|
|
* @author Kener (@Kener-林峰, kener.linfeng@gmail.com)
|
|
*
|
|
*/
|
|
define(function (require) {
|
|
var ChartBase = require('./base');
|
|
|
|
// 图形依赖
|
|
var CandleShape = require('../util/shape/Candle');
|
|
// 组件依赖
|
|
require('../component/axis');
|
|
require('../component/grid');
|
|
require('../component/dataZoom');
|
|
|
|
var ecConfig = require('../config');
|
|
// K线图默认参数
|
|
ecConfig.k = {
|
|
zlevel: 0, // 一级层叠
|
|
z: 2, // 二级层叠
|
|
clickable: true,
|
|
hoverable: true,
|
|
legendHoverLink: false,
|
|
xAxisIndex: 0,
|
|
yAxisIndex: 0,
|
|
// barWidth: null // 默认自适应
|
|
// barMaxWidth: null // 默认自适应
|
|
itemStyle: {
|
|
normal: {
|
|
color: '#fff', // 阳线填充颜色
|
|
color0: '#00aa11', // 阴线填充颜色
|
|
lineStyle: {
|
|
width: 1,
|
|
color: '#ff3200', // 阳线边框颜色
|
|
color0: '#00aa11' // 阴线边框颜色
|
|
},
|
|
label: {
|
|
show: false
|
|
// formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调
|
|
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
|
|
// 'inside'|'left'|'right'|'top'|'bottom'
|
|
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
|
|
}
|
|
},
|
|
emphasis: {
|
|
// color: 各异,
|
|
// color0: 各异,
|
|
label: {
|
|
show: false
|
|
// formatter: 标签文本格式器,同Tooltip.formatter,不支持异步回调
|
|
// position: 默认自适应,水平布局为'top',垂直布局为'right',可选为
|
|
// 'inside'|'left'|'right'|'top'|'bottom'
|
|
// textStyle: null // 默认使用全局文本样式,详见TEXTSTYLE
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
var ecData = require('../util/ecData');
|
|
var zrUtil = require('zrender/tool/util');
|
|
|
|
/**
|
|
* 构造函数
|
|
* @param {Object} messageCenter echart消息中心
|
|
* @param {ZRender} zr zrender实例
|
|
* @param {Object} series 数据
|
|
* @param {Object} component 组件
|
|
*/
|
|
function K(ecTheme, messageCenter, zr, option, myChart) {
|
|
// 图表基类
|
|
ChartBase.call(this, ecTheme, messageCenter, zr, option, myChart);
|
|
|
|
this.refresh(option);
|
|
}
|
|
|
|
K.prototype = {
|
|
type: ecConfig.CHART_TYPE_K,
|
|
/**
|
|
* 绘制图形
|
|
*/
|
|
_buildShape: function () {
|
|
var series = this.series;
|
|
this.selectedMap = {};
|
|
|
|
// 水平垂直双向series索引 ,position索引到seriesIndex
|
|
var _position2sIndexMap = {
|
|
top: [],
|
|
bottom: []
|
|
};
|
|
var xAxis;
|
|
for (var i = 0, l = series.length; i < l; i++) {
|
|
if (series[i].type === ecConfig.CHART_TYPE_K) {
|
|
series[i] = this.reformOption(series[i]);
|
|
this.legendHoverLink = series[i].legendHoverLink || this.legendHoverLink;
|
|
xAxis = this.component.xAxis.getAxis(series[i].xAxisIndex);
|
|
if (xAxis.type === ecConfig.COMPONENT_TYPE_AXIS_CATEGORY
|
|
) {
|
|
_position2sIndexMap[xAxis.getPosition()].push(i);
|
|
}
|
|
}
|
|
}
|
|
//console.log(_position2sIndexMap)
|
|
for (var position in _position2sIndexMap) {
|
|
if (_position2sIndexMap[position].length > 0) {
|
|
this._buildSinglePosition(
|
|
position, _position2sIndexMap[position]
|
|
);
|
|
}
|
|
}
|
|
|
|
this.addShapeList();
|
|
},
|
|
|
|
/**
|
|
* 构建单个方向上的K线图
|
|
*
|
|
* @param {number} seriesIndex 系列索引
|
|
*/
|
|
_buildSinglePosition: function (position, seriesArray) {
|
|
var mapData = this._mapData(seriesArray);
|
|
var locationMap = mapData.locationMap;
|
|
var maxDataLength = mapData.maxDataLength;
|
|
|
|
if (maxDataLength === 0 || locationMap.length === 0) {
|
|
return;
|
|
}
|
|
this._buildHorizontal(seriesArray, maxDataLength, locationMap);
|
|
|
|
for (var i = 0, l = seriesArray.length; i < l; i++) {
|
|
this.buildMark(seriesArray[i]);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* 数据整形
|
|
* 数组位置映射到系列索引
|
|
*/
|
|
_mapData: function (seriesArray) {
|
|
var series = this.series;
|
|
var serie; // 临时映射变量
|
|
var serieName; // 临时映射变量
|
|
var legend = this.component.legend;
|
|
var locationMap = []; // 需要返回的东西:数组位置映射到系列索引
|
|
var maxDataLength = 0; // 需要返回的东西:最大数据长度
|
|
// 计算需要显示的个数和分配位置并记在下面这个结构里
|
|
for (var i = 0, l = seriesArray.length; i < l; i++) {
|
|
serie = series[seriesArray[i]];
|
|
serieName = serie.name;
|
|
this.selectedMap[serieName] = legend
|
|
? legend.isSelected(serieName)
|
|
: true;
|
|
|
|
if (this.selectedMap[serieName]) {
|
|
locationMap.push(seriesArray[i]);
|
|
}
|
|
// 兼职帮算一下最大长度
|
|
maxDataLength = Math.max(maxDataLength, serie.data.length);
|
|
}
|
|
return {
|
|
locationMap: locationMap,
|
|
maxDataLength: maxDataLength
|
|
};
|
|
},
|
|
|
|
/**
|
|
* 构建类目轴为水平方向的K线图系列
|
|
*/
|
|
_buildHorizontal: function (seriesArray, maxDataLength, locationMap) {
|
|
var series = this.series;
|
|
// 确定类目轴和数值轴,同一方向随便找一个即可
|
|
var seriesIndex;
|
|
var serie;
|
|
var xAxisIndex;
|
|
var categoryAxis;
|
|
var yAxisIndex; // 数值轴各异
|
|
var valueAxis; // 数值轴各异
|
|
|
|
var pointList = {};
|
|
var candleWidth;
|
|
var data;
|
|
var value;
|
|
var barMaxWidth;
|
|
for (var j = 0, k = locationMap.length; j < k; j++) {
|
|
seriesIndex = locationMap[j];
|
|
serie = series[seriesIndex];
|
|
|
|
xAxisIndex = serie.xAxisIndex || 0;
|
|
categoryAxis = this.component.xAxis.getAxis(xAxisIndex);
|
|
candleWidth = serie.barWidth
|
|
|| Math.floor(categoryAxis.getGap() / 2);
|
|
barMaxWidth = serie.barMaxWidth;
|
|
if (barMaxWidth && barMaxWidth < candleWidth) {
|
|
candleWidth = barMaxWidth;
|
|
}
|
|
yAxisIndex = serie.yAxisIndex || 0;
|
|
valueAxis = this.component.yAxis.getAxis(yAxisIndex);
|
|
|
|
pointList[seriesIndex] = [];
|
|
for (var i = 0, l = maxDataLength; i < l; i++) {
|
|
if (categoryAxis.getNameByIndex(i) == null) {
|
|
// 系列数据超出类目轴长度
|
|
break;
|
|
}
|
|
|
|
data = serie.data[i];
|
|
value = this.getDataFromOption(data, '-');
|
|
if (value === '-' || value.length != 4) {
|
|
// 数据格式不符
|
|
continue;
|
|
}
|
|
pointList[seriesIndex].push([
|
|
categoryAxis.getCoordByIndex(i), // 横坐标
|
|
candleWidth,
|
|
valueAxis.getCoord(value[0]), // 纵坐标:开盘
|
|
valueAxis.getCoord(value[1]), // 纵坐标:收盘
|
|
valueAxis.getCoord(value[2]), // 纵坐标:最低
|
|
valueAxis.getCoord(value[3]), // 纵坐标:最高
|
|
i, // 数据index
|
|
categoryAxis.getNameByIndex(i) // 类目名称
|
|
]);
|
|
}
|
|
}
|
|
// console.log(pointList)
|
|
this._buildKLine(seriesArray, pointList);
|
|
},
|
|
|
|
/**
|
|
* 生成K线
|
|
*/
|
|
_buildKLine: function (seriesArray, pointList) {
|
|
var series = this.series;
|
|
// normal:
|
|
var nLineWidth;
|
|
var nLineColor;
|
|
var nLineColor0; // 阴线
|
|
var nColor;
|
|
var nColor0; // 阴线
|
|
|
|
// emphasis:
|
|
var eLineWidth;
|
|
var eLineColor;
|
|
var eLineColor0;
|
|
var eColor;
|
|
var eColor0;
|
|
|
|
var serie;
|
|
var queryTarget;
|
|
var data;
|
|
var seriesPL;
|
|
var singlePoint;
|
|
var candleType;
|
|
|
|
var seriesIndex;
|
|
for (var sIdx = 0, len = seriesArray.length; sIdx < len; sIdx++) {
|
|
seriesIndex = seriesArray[sIdx];
|
|
serie = series[seriesIndex];
|
|
seriesPL = pointList[seriesIndex];
|
|
|
|
if (this._isLarge(seriesPL)) {
|
|
seriesPL = this._getLargePointList(seriesPL);
|
|
}
|
|
|
|
if (serie.type === ecConfig.CHART_TYPE_K && seriesPL != null) {
|
|
// 多级控制
|
|
queryTarget = serie;
|
|
nLineWidth = this.query(
|
|
queryTarget, 'itemStyle.normal.lineStyle.width'
|
|
);
|
|
nLineColor = this.query(
|
|
queryTarget, 'itemStyle.normal.lineStyle.color'
|
|
);
|
|
nLineColor0 = this.query(
|
|
queryTarget, 'itemStyle.normal.lineStyle.color0'
|
|
);
|
|
nColor = this.query(
|
|
queryTarget, 'itemStyle.normal.color'
|
|
);
|
|
nColor0 = this.query(
|
|
queryTarget, 'itemStyle.normal.color0'
|
|
);
|
|
|
|
eLineWidth = this.query(
|
|
queryTarget, 'itemStyle.emphasis.lineStyle.width'
|
|
);
|
|
eLineColor = this.query(
|
|
queryTarget, 'itemStyle.emphasis.lineStyle.color'
|
|
);
|
|
eLineColor0 = this.query(
|
|
queryTarget, 'itemStyle.emphasis.lineStyle.color0'
|
|
);
|
|
eColor = this.query(
|
|
queryTarget, 'itemStyle.emphasis.color'
|
|
);
|
|
eColor0 = this.query(
|
|
queryTarget, 'itemStyle.emphasis.color0'
|
|
);
|
|
|
|
/*
|
|
* pointlist=[
|
|
* 0 x,
|
|
* 1 width,
|
|
* 2 y0,
|
|
* 3 y1,
|
|
* 4 y2,
|
|
* 5 y3,
|
|
* 6 dataIndex,
|
|
* 7 categoryName
|
|
* ]
|
|
*/
|
|
for (var i = 0, l = seriesPL.length; i < l; i++) {
|
|
singlePoint = seriesPL[i];
|
|
data = serie.data[singlePoint[6]];
|
|
queryTarget = data;
|
|
candleType = singlePoint[3] < singlePoint[2];
|
|
this.shapeList.push(this._getCandle(
|
|
seriesIndex, // seriesIndex
|
|
singlePoint[6], // dataIndex
|
|
singlePoint[7], // name
|
|
|
|
singlePoint[0], // x
|
|
singlePoint[1], // width
|
|
singlePoint[2], // y开盘
|
|
singlePoint[3], // y收盘
|
|
singlePoint[4], // y最低
|
|
singlePoint[5], // y最高
|
|
|
|
// 填充颜色
|
|
candleType
|
|
? (this.query( // 阳
|
|
queryTarget, 'itemStyle.normal.color'
|
|
) || nColor)
|
|
: (this.query( // 阴
|
|
queryTarget, 'itemStyle.normal.color0'
|
|
) || nColor0),
|
|
|
|
// 线宽
|
|
this.query(
|
|
queryTarget, 'itemStyle.normal.lineStyle.width'
|
|
) || nLineWidth,
|
|
|
|
// 线色
|
|
candleType
|
|
? (this.query( // 阳
|
|
queryTarget,
|
|
'itemStyle.normal.lineStyle.color'
|
|
) || nLineColor)
|
|
: (this.query( // 阴
|
|
queryTarget,
|
|
'itemStyle.normal.lineStyle.color0'
|
|
) || nLineColor0),
|
|
|
|
//------------高亮
|
|
|
|
// 填充颜色
|
|
candleType
|
|
? (this.query( // 阳
|
|
queryTarget, 'itemStyle.emphasis.color'
|
|
) || eColor || nColor)
|
|
: (this.query( // 阴
|
|
queryTarget, 'itemStyle.emphasis.color0'
|
|
) || eColor0 || nColor0),
|
|
|
|
// 线宽
|
|
this.query(
|
|
queryTarget, 'itemStyle.emphasis.lineStyle.width'
|
|
) || eLineWidth || nLineWidth,
|
|
|
|
// 线色
|
|
candleType
|
|
? (this.query( // 阳
|
|
queryTarget,
|
|
'itemStyle.emphasis.lineStyle.color'
|
|
) || eLineColor || nLineColor)
|
|
: (this.query( // 阴
|
|
queryTarget,
|
|
'itemStyle.emphasis.lineStyle.color0'
|
|
) || eLineColor0 || nLineColor0)
|
|
));
|
|
}
|
|
}
|
|
}
|
|
// console.log(this.shapeList)
|
|
},
|
|
|
|
_isLarge: function(singlePL) {
|
|
return singlePL[0][1] < 0.5;
|
|
},
|
|
|
|
/**
|
|
* 大规模pointList优化
|
|
*/
|
|
_getLargePointList: function(singlePL) {
|
|
var total = this.component.grid.getWidth();
|
|
var len = singlePL.length;
|
|
var newList = [];
|
|
for (var i = 0; i < total; i++) {
|
|
newList[i] = singlePL[Math.floor(len / total * i)];
|
|
}
|
|
return newList;
|
|
},
|
|
|
|
/**
|
|
* 生成K线图上的图形
|
|
*/
|
|
_getCandle: function (
|
|
seriesIndex, dataIndex, name,
|
|
x, width, y0, y1, y2, y3,
|
|
nColor, nLinewidth, nLineColor,
|
|
eColor, eLinewidth, eLineColor
|
|
) {
|
|
var series = this.series;
|
|
var serie = series[seriesIndex];
|
|
var data = serie.data[dataIndex];
|
|
var queryTarget = [data, serie];
|
|
|
|
var itemShape = {
|
|
zlevel: this.getZlevelBase(),
|
|
z: this.getZBase(),
|
|
clickable: this.deepQuery(queryTarget, 'clickable'),
|
|
hoverable: this.deepQuery(queryTarget, 'hoverable'),
|
|
style: {
|
|
x: x,
|
|
y: [y0, y1, y2, y3],
|
|
width: width,
|
|
color: nColor,
|
|
strokeColor: nLineColor,
|
|
lineWidth: nLinewidth,
|
|
brushType: 'both'
|
|
},
|
|
highlightStyle: {
|
|
color: eColor,
|
|
strokeColor: eLineColor,
|
|
lineWidth: eLinewidth
|
|
},
|
|
_seriesIndex: seriesIndex
|
|
};
|
|
|
|
itemShape = this.addLabel(itemShape, serie, data, name);
|
|
|
|
ecData.pack(
|
|
itemShape,
|
|
serie, seriesIndex,
|
|
data, dataIndex,
|
|
name
|
|
);
|
|
|
|
itemShape = new CandleShape(itemShape);
|
|
|
|
return itemShape;
|
|
},
|
|
|
|
// 位置转换
|
|
getMarkCoord: function (seriesIndex, mpData) {
|
|
var serie = this.series[seriesIndex];
|
|
var xAxis = this.component.xAxis.getAxis(serie.xAxisIndex);
|
|
var yAxis = this.component.yAxis.getAxis(serie.yAxisIndex);
|
|
|
|
return [
|
|
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)
|
|
];
|
|
},
|
|
|
|
/**
|
|
* 刷新
|
|
*/
|
|
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 = {}; // seriesIndex索引参数
|
|
for (var i = 0, l = params.length; i < l; i++) {
|
|
aniMap[params[i][0]] = params[i];
|
|
}
|
|
var x;
|
|
var dx;
|
|
var y;
|
|
var serie;
|
|
var seriesIndex;
|
|
var dataIndex;
|
|
|
|
var aniCount = 0;
|
|
function animationDone() {
|
|
aniCount--;
|
|
if (aniCount === 0) {
|
|
done && done();
|
|
}
|
|
}
|
|
|
|
for (var i = 0, l = this.shapeList.length; i < l; i++) {
|
|
seriesIndex = this.shapeList[i]._seriesIndex;
|
|
if (aniMap[seriesIndex] && !aniMap[seriesIndex][3]) {
|
|
// 有数据删除才有移动的动画
|
|
if (this.shapeList[i].type === 'candle') {
|
|
dataIndex = ecData.get(this.shapeList[i], 'dataIndex');
|
|
serie = series[seriesIndex];
|
|
if (aniMap[seriesIndex][2]
|
|
&& dataIndex === serie.data.length - 1
|
|
) {
|
|
// 队头加入删除末尾
|
|
this.zr.delShape(this.shapeList[i].id);
|
|
continue;
|
|
}
|
|
else if (!aniMap[seriesIndex][2] && dataIndex === 0) {
|
|
// 队尾加入删除头部
|
|
this.zr.delShape(this.shapeList[i].id);
|
|
continue;
|
|
}
|
|
dx = this.component.xAxis.getAxis(
|
|
serie.xAxisIndex || 0
|
|
).getGap();
|
|
x = aniMap[seriesIndex][2] ? dx : -dx;
|
|
y = 0;
|
|
aniCount++;
|
|
this.zr.animate(this.shapeList[i].id, '')
|
|
.when(
|
|
this.query(this.option, 'animationDurationUpdate'),
|
|
{ position: [ x, y ] }
|
|
)
|
|
.done(animationDone)
|
|
.start();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 没有动画
|
|
if (!aniCount) {
|
|
animationDone();
|
|
}
|
|
}
|
|
};
|
|
|
|
zrUtil.inherits(K, ChartBase);
|
|
|
|
// 图表注册
|
|
require('../chart').define('k', K);
|
|
|
|
return K;
|
|
});
|