如何使用Echarts设计专业的K线图
先上图:

Echarts所需的K线的数据结构有:时间(date),开盘(open),收盘(close),最低(lowest),最高(highest),成交量(volume)
数据采用list列表形式封装,如:
时间(date),开盘(open),收盘(close),最低(lowest),最高(highest),成交量(volume)
var data0 = [["2016-06-17", 17733.44, 17675.16, 17602.78, 17733.44, 248680000],
["2016-06-20", 17736.87, 17804.87, 17736.87, 17946.36, 99380000],
["2016-06-21", 17827.33, 17829.73, 17799.8, 17877.84, 85130000],
["2016-06-22", 17832.67, 17780.83, 17770.36, 17920.16, 89440000]]
为了方便Echarts调用,需要将上面代码进行转换:
function splitData(rawData) {
var datas = [];
var times = [];
var vols = [];
for (var i = 0; i < rawData.length; i++) {
datas.push(rawData[i]);
times.push(rawData[i].splice(0, 1)[0]);
vols.push(rawData[i][4]);
}
return {
datas: datas,//开,收,低,高,量
times: times,//时间
vols: vols, // 量
};
}
接下来还有几个指标的公式(这是为了方便我使用而修改过的MA指标,Echarts官方样例有原版MA算法)。
MA指标:
function MA(dayCount,datas,field) {
var ma,i,l,j,sum;
ma=[];
//判断不放在循环内,提升性能
if(field){
//有字段配置
for(i = 0, l = datas.length; i < l; i++){
if(i < dayCount - 1){
ma.push(NaN);
continue;
}
sum = 0;
for(j = 0; j < dayCount; j++){
sum += datas[i - j][field];
}
ma.push(sum/dayCount);
}
}else{
//无字段配置
for(i=0,l=datas.length;i<l;i++){
if(i<dayCount-1){
ma.push(NaN);
continue;
}
sum=0;
for(j=0;j<dayCount;j++){
sum+=datas[i-j];
}
ma.push(sum/dayCount);
}
}
return [ma,dayCount];
}
MACD指标:
function EMA(n, datas, field) {
var i, l, ema, a;
a = 2 / (n + 1);
if (field) {
//二维数组
ema = [datas[0][field]];
for (i = 1, l = datas.length; i < l; i++) {
ema.push((a * datas[i][field] + (1 - a) * ema[i - 1]).toFixed(4));
}
} else {
//普通一维数组
ema = [datas[0]];
for (i = 1, l = datas.length; i < l; i++) {
ema.push((a * datas[i] + (1 - a) * ema[i - 1]).toFixed(4));
}
}
return ema;
}
function MACD(short, long, mid, datas, field) {
var i, l, dif, dea, macd, result;
result = {};
dif = [];
macd = [];
var emaShort = EMA(short,datas,field);
var emaLong = EMA(long,datas,field);
for (i = 0, l = datas.length; i < l; i++) {
dif.push((emaShort[i] - emaLong[i]).toFixed(4));
}
dea = EMA(mid, dif);
for (i = 0, l = datas.length; i < l; i++) {
macd.push(((dif[i] - dea[i]) * 2).toFixed(4));
}
result.dif = dif;
result.dea = dea;
result.macd = macd;
return result;
}
数据转换:
var data = splitData(data0);
调用MA指标和MACD指标:
macd = MACD(12, 26, 9, data.datas, 1);
MA1 = MA(20, data.datas, 1);
MA2 = MA(60, data.datas, 1);
接下来就是关键的Echarts的图形配置项 option 部分,Echarts中大家经过自行测试就会知道,可以设置鼠标跟随悬浮窗,动态 显示数据等
如上图所示,这个悬浮窗,如果想要将他固定到一个位置显示相对就要复杂一些,我研究了Echarts社区的相当多的列子都没有相应的方法来实现,经过大佬(https://www.eotodo.com/stydy/block/39.html)的启发,参考了其实现的方法。
在Echarts中,tooltip方法就是可以支持实现上图所示的原生悬浮窗,tooltip中有一个方法 tooltip.formatter 支持对悬浮窗的样式和内容进行修改,期中formatter 则是一个回调函数,它的params参数中则可以取到提示框中需要显示的数据。
tooltip配置
"tooltip": {
"show": true,
"trigger": "axis",
"triggerOn": "mousemove|click",
"axisPointer": {
"type": "line"
},
"formatter": //此处则是悬浮窗的回调方法
function(params) {
if(params.length > 0){
PosSelect(params) // 将params中的悬浮窗数据传递给PosSelect()外部函数
}
},
},
如果这里对params的数据结构不太了解,可以打印输出看你的数据结构是怎么样的
"formatter":
function(params) {
if(params.length > 0){
console.log(params) //这里插入打印输出,按F12调试
}
},

将其中数据展开,我们可以看到其结构如下:
(8) […]
0: {…}
"$vars": Array(3) [ "seriesName", "name", "value" ]
axisDim: "x"
axisId: "\u0000series\u00000\u00000"
axisIndex: 0
axisType: "xAxis.category"
axisValue: "2015-04-22"
axisValueLabel: "2015-04-22"
borderColor: "#0CF49B"
color: "#0CF49B"
componentIndex: 0
componentSubType: "candlestick"
componentType: "series"
data: Array(6) [ 1334, 17950.82, 18038.27, … ]
dataIndex: 1334
dataType: undefined
dimensionNames: Array(5) [ "base", "open", "close", … ]
encode: Object { x: (1) […], y: (4) […] }
marker: "<span style=\"display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:#0CF49B;\"></span>"
name: "2015-04-22"
seriesId: "\u0000K线图\u00000"
seriesIndex: 0
seriesName: "K线图"
seriesType: "candlestick"
value: Array(6) [ 1334, 17950.82, 18038.27, … ]
<prototype>: Object { … }
1: {…}
"$vars": Array(3) [ "seriesName", "name", "value" ]
axisDim: "x"
axisId: "\u0000series\u00000\u00000"
axisIndex: 0
axisType: "xAxis.category"
axisValue: "2015-04-22"
axisValueLabel: "2015-04-22"
borderColor: undefined
color: "#2ec7c9"
componentIndex: 1
componentSubType: "line"
componentType: "series"
data: 17903.9825
dataIndex: 1334
dataType: undefined
dimensionNames: Array [ "x", "y" ]
encode: Object { x: (1) […], y: (1) […] }
marker: "<span style=\"display:inline-block;margin-right:5px;border-radius:10px;width:10px;height:10px;background-color:#2ec7c9;\"></span>"
name: "2015-04-22"
seriesId: "\u0000MA20\u00000"
seriesIndex: 1
seriesName: "MA20"
seriesType: "line"
value: 17903.9825
<prototype>: Object { … }
取到数据那么就相对好办一些了,那么怎么来实现呢?我们可以在Echarts页面中通过javascript插入html和样式等等,这不就解决了?
在页面通过坐标系定位,将对应数据,定位在坐标系的相应位置,并将CSS样式调整美观:
$(` <div class="k-box pos-box" id='Poskdata'></div>
<div class="ma-box pos-box" id='Posmadata'></div>
<div class="vol-box pos-box" id='Posvoldata'></div>
<div class="macd-box pos-box" id='Posmacddata'></div>`).appendTo(
$('#chart-panel')
);
$('.k-box').css({
'top': '35px',
})
$('.ma-box').css({
'top': '65px',
})
$('.vol-box').css({
'top': '67%',
})
$('.macd-box').css({
'top': '79%',
})
$('.pos-box').css({
'position': 'absolute',
'left': '10px',
'z-index': '1',
'padding': '5px',
'font-size': '13px',
'color': '#ccc',
})
$('.p').css({
'padding': 0,
'margin': 0
})
引用用于显示的标签对象
var PosK = document.getElementById('Poskdata')
var PosMA = document.getElementById('Posmadata')
var PosVolSelect = document.getElementById('Posvoldata');
var PosMacdSelect = document.getElementById('Posmacddata');
同时在回调函数PosSelect()中进行数据调整
function PosSelect(params) {
let _this = this;
_this.datas={};
_this.ma = {};
_this.macd = {};
for (i = 0; i < params.length; i++) {
var el = params[i]
switch (el.seriesIndex) {
case 0:
_this.datas = {
color: el.color,
date:{
name:"时间",
value: el.name
},
open: {
name: "开",
value: el.value[1]
},
close: {
name: "收",
value: el.value[2]
},
low: {
name: "低",
value: el.value[3]
},
heigh: {
name: "高",
value: el.value[4]
},
zhangd:{
name:"涨幅",
value:((el.value[2] - el.value[1]) / el.value[1] * 100).toFixed(2),
color: el.color,
},
zhenf:{
name:"振幅",
value:(Math.abs(el.value[4] - el.value[3]) / el.value[3] * 100).toFixed(2)
}
};
break;
case 1:
_this.ma.ma5 = {
name: "MA5",
value: el.value.toFixed(4),
color: el.color
};
break;
case 2:
_this.ma.ma10 = {
name: "MA10",
value: el.value.toFixed(4),
color: el.color
};
break;
case 3:
_this.ma.ma20 = {
name: "MA20",
value: el.value.toFixed(4),
color: el.color
};
break;
case 4:
_this.ma.ma30 = {
name: "MA30",
value: el.value.toFixed(4),
color: el.color
};
break;
case 4:
break;
case 5:
break;
case 6:
break;
case 7:
_this.vol = {
name: el.seriesName,
value: el.value,
color: el.color
}
break;
case 8:
_this.macd.macd = {
name: el.seriesName,
value: el.value,
color: el.color
}
break;
case 9:
_this.macd.dif = {
name: el.seriesName,
value: el.value,
color: el.color
}
break;
case 10:
_this.macd.dea = {
name: el.seriesName,
value: el.value,
color: el.color
}
break;
default:
break;
}
}
// 为了使显示的内容能够根据指标开关自动显示或关闭,所以如下方式实现
var objk = Object.keys(_this.datas);
var ak = [];
for(j=1;j<objk.length;j++){
ak.push(`${_this.datas[objk[j]].name}:<span style="color:${_this.datas[objk[j]].color}">${_this.datas[objk[j]].value}</span> `);
}
PosK.innerHTML = "<p>"+ak.join('')+"</p>"; //将叠加好的html输入到指定ID的div中
var objma = Object.keys(_this.ma);
var at=[];
for(j=0;j<objma.length;j++){
at.push(`<span style="color:${_this.ma[objma[j]].color}">${_this.ma[objma[j]].name}:${_this.ma[objma[j]].value}</span> `);
}
PosMA.innerHTML = at.join('');
PosVolSelect.innerHTML = `${_this.vol.name}:<span style="color:${_this.vol.color}">${_this.vol.value}</span> `;
var objmacd = Object.keys(_this.macd);
var acd = [];
for(j=0;j<objmacd.length;j++){
acd.push(`${_this.macd[objmacd[j]].name}:<span style="color:${_this.macd[objmacd[j]].color}">${_this.macd[objmacd[j]].value}</span> `)
}
PosMacdSelect.innerHTML = acd.join('');
}
最终完美实现图1的效果,完整项目公开在 Echartsjs 中,如果大家觉得有可以改进的地方可以留言或直接在Echarts中进行评论,【Echartsjs官网测试已关闭】
此项目已经运用在我所开发的量化软件中
后续我会更新Python嵌入Echarts实现专业K线图标显示
最后上个我开发的量化软件截图:





本文介绍如何使用Echarts设计专业的K线图,包括MA和MACD指标的实现,数据转换,以及自定义悬浮窗的技巧。通过坐标系定位数据,结合CSS美化,实现动态显示。该项目已应用于量化软件。
797





