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

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

如何使用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>&nbsp;`);
    }
    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>&nbsp;`);
    }
    PosMA.innerHTML = at.join('');
    
    PosVolSelect.innerHTML = `${_this.vol.name}:<span style="color:${_this.vol.color}">${_this.vol.value}</span>&nbsp;`;
    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>&nbsp;`)
    }
    PosMacdSelect.innerHTML = acd.join('');
}

最终完美实现图1的效果,完整项目公开在 Echartsjs 中,如果大家觉得有可以改进的地方可以留言或直接在Echarts中进行评论,【Echartsjs官网测试已关闭】

此项目已经运用在我所开发的量化软件中

后续我会更新Python嵌入Echarts实现专业K线图标显示
最后上个我开发的量化软件截图:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论 6
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DaoYuanTech

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值