前言
平常为了方便看行情就会打开小程序看走势,作为一个开发在看腾讯自选股的日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线实体高度计算* 柱子是从上到下绘下去的,那么要做的是先确定到底是开盘在上还是收盘在上
?* 情况一:收盘 < 开盘
柱体是绿色的,那么开盘就是实体的最高点* 情况一:收盘 > 开盘
柱体是红色的,那么收盘就是实体的最高点* 如果是绿柱那么开盘在上
如果是红柱那么收盘在上
* 那么怎么计算柱体高度呢?我这里的话是用开盘的位置 - 收盘的位置得出来的差就是柱体高度,需要转换成绝对值。* 影线的高度计算逻辑同上,唯一不同的是影线是根据最高点以及最低点进行计算的,把上面的开盘、收盘换成最低、最高即可。
那么现在知道了:
- 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,在用这个计算就会超出画布// 所以现在换成传进来的widtdconst { 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线之前得先了解下规律、规则才方便画出来。
- 如果画出来的图太模糊,就把画布放大。
- 只要刻度算的准确,难题就差不多都解决了。
最后
整理了75个JS高频面试题,并给出了答案和解析,基本上可以保证你能应付面试官关于JS的提问。
有需要的小伙伴,可以点击下方卡片领取,无偿分享