前言
平常为了方便看行情就会打开小程序看走势,作为一个开发在看腾讯自选股的日K时就会在想这个玩意是怎么弄的呢?下面我就用h5来实现一个最简K线图。
K线的构成以及画法
- K线又称阴阳线、棒线、红黑线或蜡烛线。
- K线是一条柱状的线条,由实体和影线两部分构成。影线在实体上方的部分叫上影线,下方的部分叫下影线。实体分阳线和阴线。其中影线表明当天交易的最高和最低价,而实体表明当天的开盘价和收盘价。 如果收盘价高于开盘价,K线就用红色或者空心显示,称为阳线;反之,收盘价低于开盘价,K线用绿色或实心显示,称为阴线。
了解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;
})()
获取数据、处理数据
数据我们从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线实体高度计算
- 柱子是从上到下绘下去的,那么要做的是先确定到底是
开盘在上还是收盘在上
? - 情况一:
收盘 < 开盘
柱体是绿色的,那么开盘就是实体的最高点 - 情况一:
收盘 > 开盘
柱体是红色的,那么收盘就是实体的最高点 - 如果是
绿柱那么开盘在上
如果是红柱那么收盘在上
- 那么怎么计算柱体高度呢?我这里的话是用开盘的位置 - 收盘的位置得出来的差就是柱体高度,需要转换成绝对值。
- 柱子是从上到下绘下去的,那么要做的是先确定到底是
- 影线的高度计算逻辑同上,唯一不同的是影线是根据最高点以及最低点进行计算的,把上面的开盘、收盘换成最低、最高即可。
- K线实体高度计算
那么现在知道了:
- x轴的刻度
- y轴的刻度
- 柱体颜色
- 柱体长度以及影线长度
看到这里你就已经接近成功了。这里我说下我的数据格式[33,56,89]
是这种格式的,因为在实际的业务当中数据量很大,如果都用字段标出来的话会浪费带宽。
下标 | 意思 |
---|---|
1 | 开盘 |
2 | 收盘 |
3 | 最低 |
4 | 最高 |
5 | 成交量 |
6 | MA5 |
7 | MA10 |
8 | MA20 |
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宽度是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线图一下就能看出清晰度的区别。
绘制MA5、10、20日均线
什么是均线?
对过去某个时间段的收盘价进行普通平均。比如20日均线,是将过去20个交易日的收盘价相加然后除以20,就得到一个值;再以昨日向前倒推20个交易日,同样的方法计算出另外一个值,以此类推,5日均线,10日均线也由此得来,将这些值连接起来,就形成一个普通均线。
不知道腾讯自选股的均线颜色,我就随便用了三种。均线就是折线。具体画法参考手动实现Antv F2的折线图
绘制成交量
- 成交量的实现和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();
}
总结
我这个只是比较简单的一个K线,里面还有很多细节、功能没有实现。例如十字架、拖动分页等。我大概总结下:
- 画K线之前得先了解下规律、规则才方便画出来。
- 如果画出来的图太模糊,就把画布放大。
- 只要刻度算的准确,难题就差不多都解决了。