likes
comments
collection
share

使用canvas实现腾讯自选股K线图

作者站长头像
站长
· 阅读数 30

前言

平常为了方便看行情就会打开小程序看走势,作为一个开发在看腾讯自选股的日K时就会在想这个玩意是怎么弄的呢?下面我就用h5来实现一个最简K线图。

K线的构成以及画法

  • K线又称阴阳线、棒线、红黑线或蜡烛线。
  • K线是一条柱状的线条,由实体和影线两部分构成。影线在实体上方的部分叫上影线,下方的部分叫下影线。实体分阳线和阴线。其中影线表明当天交易的最高和最低价,而实体表明当天的开盘价和收盘价。 如果收盘价高于开盘价,K线就用红色或者空心显示,称为阳线;反之,收盘价低于开盘价,K线用绿色或实心显示,称为阴线。

使用canvas实现腾讯自选股K线图

了解K线的构成对我们进行绘制有很大的帮助。

效果对比

左边的是我们实现的,右边的是腾讯自选股的。 使用canvas实现腾讯自选股K线图

绘制过程

  • 绘制网格
  • 获取数据、处理数据
  • 绘制K线实体、影线
  • 绘制MA5、10、20日均线
  • 绘制成交量

绘制网格

canvas的宽高由父级定义然后进行动态设置。这样做的好处是外层宽高变了,canvas自动适应。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    .dayLine {
        width: 353px;
        height: 293px;
        margin: 30px auto;
    }
</style>
<body>
    <div class="dayLine">
        <canvas id="canvas"></canvas>
    </div>
    <script src="./tools/dayLine.js"></script>
    <script>
        const parent = document.getElementsByClassName('dayLine')[0];
        new dayLine({
            canvas: document.getElementById('canvas'),
            height: parent.clientHeight,
            width: parent.clientWidth
        })
    </script>
</body>
</html>
字段意思
topHight日K区域
space日K成交量分隔界
botHight成交量区域
;(function(){
    function dayLine(options) {
        const { canvas, height, width } = options;
        canvas.width = width;
        canvas.height = height;
        this.canvas = canvas;
        this.ctx = canvas.getContext('2d');
        this.topHight = 202;        // 顶部日k区域
        this.space = 20;            // 中间空隙
        this.botHight = 71;         // 底部成交量
        this.init();
    }
    dayLine.prototype.init = function() {
        // 绘制分时网格
	this.mineLine(4);
        // 绘制成交量网格
	this.dealLine(2);
        // 绘制网格竖线
	this.verticalLine(3);
    }
    dayLine.prototype.mineLine = function(num) {
        const { ctx, topHight, canvas: { width } } = this;
        this.topScale = topHight / num;
        ctx.save();
        ctx.lineWidth = 1;
        ctx.translate(0.5, 0.5);
        ctx.strokeStyle = '#F4F5F6';
        for(let i = 1; i <= num; i++) {
            this.drawLine(0, i * this.topScale, width, i * this.topScale);
        }
        this.drawLine(0, 1, width, 1);
        ctx.restore();
    }
    window.dayLine = dayLine;
})()

使用canvas实现腾讯自选股K线图

获取数据、处理数据

数据我们从mock.js生成,地址为https://mock.mengxuegu.com/mock/62f719a8f2652f239bd0a7d1/ds/dayLinex

fetch('https://mock.mengxuegu.com/mock/62f719a8f2652f239bd0a7d1/ds/dayLinex').then( res => {
    return res.json();
}).then( res => {
    const parent = document.getElementsByClassName('dayLine')[0];
    const { data } = res;
    new dayLine({
        canvas: document.getElementById('canvas'),
        height: parent.clientHeight,
        width: parent.clientWidth,
        data: data
    })
})

获取完数据后将数据传入插件中。

绘制K线实体、影线

了解这几个点,就解除了K线的难点

  • 先确定实体以及影线的颜色只需要记住收盘 < 开盘 = 绿色反过来就是红色
  • 根据开盘、收盘、最高、最低来确定实体以及影线的高度
    • K线实体高度计算
      • 柱子是从上到下绘下去的,那么要做的是先确定到底是开盘在上还是收盘在上
      • 情况一:收盘 < 开盘柱体是绿色的,那么开盘就是实体的最高点
      • 情况一:收盘 > 开盘柱体是红色的,那么收盘就是实体的最高点
      • 如果是绿柱那么开盘在上如果是红柱那么收盘在上
      • 那么怎么计算柱体高度呢?我这里的话是用开盘的位置 - 收盘的位置得出来的差就是柱体高度,需要转换成绝对值。
    • 影线的高度计算逻辑同上,唯一不同的是影线是根据最高点以及最低点进行计算的,把上面的开盘、收盘换成最低、最高即可。

那么现在知道了:

  • x轴的刻度
  • y轴的刻度
  • 柱体颜色
  • 柱体长度以及影线长度

看到这里你就已经接近成功了。这里我说下我的数据格式[33,56,89]是这种格式的,因为在实际的业务当中数据量很大,如果都用字段标出来的话会浪费带宽。

下标意思
1开盘
2收盘
3最低
4最高
5成交量
6MA5
7MA10
8MA20
dayLine.prototype.drawDayRect = function() {
    const { ctx, data: { list }, xScale, topHight, fontSize, maxVal, minVal } = this;
    const yScale = this.markY({
        height: topHight,
        maxVal,
        minVal
    })
    for(let i = 0; i < list.length; i++) {
        let barX = i * xScale,		// 柱状x坐标
            barY = 0,				// 柱状y坐标
            barW = (xScale - 0.8),  // 柱状宽度
            lineY = 0,				// 影线y坐标
            collect = 0,			// 存储计算柱状高度数据
            barH = 0,				// 柱状高度
            lineH = 0;				// 影线高度
        // 收盘 < 开盘 = 绿色
        if (list[i][2] < list[i][1]) {
            // 如果当天是跌的则从开盘开始往下绘制
            barY = (maxVal - list[i][1]) * yScale;
            collect = (maxVal - list[i][2]) * yScale;
        } else {
            // 如果当天是涨的则从收盘开始往下绘制
            barY = (maxVal - list[i][2]) * yScale;
            collect = (maxVal - list[i][1]) * yScale;
        }

        // 计算影线长度
        lineY = (maxVal - list[i][4]) * yScale;
        lineH = Math.abs(lineY - (maxVal - list[i][3]) * yScale);

        // 如果柱体小于1 则默认为1不然会绘制不出柱体
        barH = Math.abs(barY - collect) < 0.5 ? 1 : Math.abs(barY - collect);
        ctx.beginPath();
        ctx.fillStyle = list[i][2] < list[i][1] ? '#02BD85' : '#FE5269';
        // 开盘 - 收盘的绝对值就是柱体高度
        ctx.rect(barX, barY, barW, barH);
        // 绘制影线
        ctx.rect((barX - 0.8) + (barW / 2) + 0.25, lineY, 1, lineH);
        ctx.fill();
    }
}

上面的x轴刻度减0.8是因为要在柱体之间留点空隙,避免粘在一起。

使用canvas实现腾讯自选股K线图 但是看上去有点不太对劲,感觉自己眼睛花了,看上去太模糊了。那么为什么会模糊?

模糊原因

假设我画的canvas宽度是375px,那么刚好iphone6的宽度也是375px,ok那此时是不会模糊的因为没有被拉伸。但是当另一个设备的宽度是400px画布就会被拉伸,拉伸则必然会模糊。

解决方法

我这里的解决方法是:

  • 根据屏幕分辨率将画布放大。假设分辨率是2则375*2=750,画布是750的宽
  • 但实际上我的绘制区域仍然是375,我只需要将我的375直接放大到750那不就成变清晰了吗。
  • ctx.scale(2,2)2替换成分辨率即可。
  • 然后刚才我们上面的利用宽度计算的刻度也得进行修改。
dayLine.prototype.mineLine = function(num) {
    // 之前是canvas: { width }乘以分辨率后它的值是750,在用这个计算就会超出画布
    // 所以现在换成传进来的widtd
    const { ctx, topHight, width } = this;
    this.topScale = topHight / num;
    ctx.save();
    ctx.lineWidth = 1;
    ctx.translate(0.5, 0.5);
    ctx.strokeStyle = '#F4F5F6';
    for(let i = 1; i <= num; i++) {
        this.drawLine(0, i * this.topScale, width, i * this.topScale);
    }
    this.drawLine(0, 1, width, 1);
    ctx.restore();
}

对比上下两张K线图一下就能看出清晰度的区别。

使用canvas实现腾讯自选股K线图

绘制MA5、10、20日均线

什么是均线?

对过去某个时间段的收盘价进行普通平均。比如20日均线,是将过去20个交易日的收盘价相加然后除以20,就得到一个值;再以昨日向前倒推20个交易日,同样的方法计算出另外一个值,以此类推,5日均线,10日均线也由此得来,将这些值连接起来,就形成一个普通均线。

不知道腾讯自选股的均线颜色,我就随便用了三种。均线就是折线。具体画法参考手动实现Antv F2的折线图

使用canvas实现腾讯自选股K线图

绘制成交量

  • 成交量的实现和k线实体差不多,但是更简单。
  • 颜色的话还是收盘 < 开盘 = 绿色反过来就是红色。
  • 柱体高度的话直接值 * 刻度就能拿到。
dayLine.prototype.drawTurnover = function() {
    const { data: { list }, ctx, xScale, botHight, tMaxVal, tMinVal, topHight, space, width } = this;
    let maxTurnover = [];
    let yScale = this.markY({
        height: botHight,
        maxVal: tMaxVal,
        minVal: tMinVal
    });
    ctx.save();
    ctx.lineWidth = 1;
    for(let i = 0; i < list.length; i++) {
        let x = i * xScale,
            y = (topHight + space) + (tMaxVal - list[i][5]) * yScale,
            w = (xScale - 0.8);
        maxTurnover.push(list[i][14].replace(/万/,''));
        ctx.fillStyle = list[i][2] < list[i][1] ? '#02BD85' : '#FE5269';
        ctx.beginPath();
        ctx.rect(x, y, w, list[i][5] * yScale);
        ctx.fill();
    }
    const text = `${Math.max(...maxTurnover)}万`;
    ctx.fillStyle = "#909399";
    ctx.fillText(text, width - ctx.measureText(text).width - 4, topHight + space + 14);
    ctx.restore();
}

使用canvas实现腾讯自选股K线图

总结

我这个只是比较简单的一个K线,里面还有很多细节、功能没有实现。例如十字架、拖动分页等。我大概总结下:

  • 画K线之前得先了解下规律、规则才方便画出来。
  • 如果画出来的图太模糊,就把画布放大。
  • 只要刻度算的准确,难题就差不多都解决了。