h5行情k线开发
前言
由于公司项目需要,要做港股行情的H5版本,经过分析需求,大致有两块难点: 一是行情的推送接收,二是行情K线的生成及相关操作。本文章主要分析行情K线的相关实现,由于我们前端团队之前是没有相关的工作经验的,所以我们第一反应就是去网上搜现成的插件或者相关文档。经过查找我们发现其实网上这方面的资料不多,相关插件也是比较少,比较符合的相关插件有tradingView以及百度团队开发的ECharts, 但是两者插件体积比较大而且在H5移动端的处理并不是特别好。经过讨论我们决定自研开发。
线上效果
下面是我们H5线上行情系统的实际操作图, 也可以扫码体验。


关键点分析
开发这套行情的K线图表,关键点主要有两点,其一是K线图,其二是手势的处理。K线图难度不是很大在熟悉Canvas画图基础的情况下注意不同区域划分和层级即可,重点在于数据的一些计算和判断;手势的处理就比较麻烦了需要考虑到长按,滑动,放大缩小,惯性滑动,触底加载,横屏等场景。下面就这些关键点进行逐一分析。
具体实现
一,K线图基础
1、K线图基于Z轴(可以理解成css样式中的z-index)分成了三层:
第一层画坐标轴的各种文字和线条包括边框线,XY轴分割线,X轴时间和日期,Y轴价格和成交量成交额等数据文本;

第二层画主体数据图包括分时的走势线,分时的均线,日K的柱状图,MA5,MA10,MA20走势线,最高价,最低价,成交量或成交额的柱状图等K线主体数据图或文本;

第三层画长按K线时出现的十字线及十字线的数据文本。

最后将三层相对定位在同一坐标即可。
2、Z轴每一层基于Y轴分成了三部分:
第一部分画上方走势图的线条,图形,文本;
第二部分画中间时间或日期文本;
第三部分画下方成交量或成交额的线条,图形,文本。

以上canvas的颜色、大小、线条粗细写成插件配置形式即可。
3、几个需要注意的图形画法
3.1、分时图的画法逻辑:从第一个数据点的坐标(x0, y0)画笔开始(beginPath)移动到(moveTo)下一个点的坐标(x1, y1)依次移动到最后一个点的坐标(xn, yn),到最后一点的坐标后移动到第一部分的最右下方点(width, height)然后再移动到第一部分最左下方点(0, height)最后回到起点(x0, y0)形成闭合填充(fill)渐变色(createLinearGradient)关闭画笔(closePath)。

3.2、K线柱状图:这里首先介绍下柱状图(可能有点多余)

转换成canvas画图角度其实就是线条和矩形的结合,线条的画法比较容易,中心柱状图需要注意的地方是如果是空心柱状图需要叠加两层矩形第一层的背景色和你主背景色一致第二层画边框矩形边框颜色画对应的涨跌颜色(这里是红涨绿跌)所以边框颜色设置成红色。下面是一段伪代码:
var rectConf = {
xAxis: 10, // 矩形框x轴坐标
yAxis: 20, // 矩形框y轴坐标
width: 5, // 矩形框宽度
height: 30, // 矩形框高度
}
if (!this.isSolidCandle) { // 如果不是实心蜡烛图
this.ctx.fillStyle = this.COLORS.MAINBG; // 主背景色
this.ctx.fillRect(rectConf.xAxis, rectConf.yAxis, rectConf.width, rectConf.height);
this.ctx.strokeRect(rectConf.xAxis, rectConf.yAxis, rectConf.width, rectConf.height);
this.ctx.fillStyle = lineColor; // 线条颜色
} else {
this.ctx.fillStyle = lineColor; // 线条颜色
this.ctx.fillRect(rectConf.xAxis, rectConf.yAxis, rectConf.width, rectConf.height);
}
其它可能存在难点的地方更多是涉及到计算,例如最高点最低点坐标位置,第二部分时间文本坐标位置及宽度等,这里就不一一介绍了,有问题可以下方留言。
二,手势事件处理
1、长按事件:
我们知道js中是没有这个事件的,但是是有触摸事件,所以这里利用触摸事件来模拟长按事件。定义从触摸开始超过200ms不动即为长按,可以在触摸事件中使用setTimeout定时器超过200ms即执行长按事件,并且设置长按标识,但是这里需要注意的是在滑动事件中清除这个定时器,如果长按事件已经执行那么清除了也不会有影响,如果还没执行说明还没到达长按的条件,利用这个特性就能模拟长按事件了,下面是一段伪代码:
// 触摸开始事件
touchStartEvent(e) {
this.longTapTimeout = setTimeout(() => {
this.longTapFlag = true;
this.longTap(touchOne);
}, 200);
}, // 触摸移动事件
touchMoveEvent(e) {
if (this.longTapTimeout) {
clearTimeout(this.longTapTimeout);
this.longTapTimeout = null;
}
if (this.longTapFlag) {
// 长按滑动事件(即十字线滑动事件)
this.touchMove();
} else {
// k线滑动事件
this.swipe();
}
}
这个事件是双指事件,在js中是可以通过event.targetTouches的长度来判断的。实现放大缩小大体思路是:
step1:计算两指中间坐标点;
step2:计算两指间的直线距离;
step3:根据直线距离以及上一次的直线距离计算需要放大或缩小的刻度;
step4:计算刻度不变时中间坐标点对应K线数组的索引index1;
step5:计算刻度变动后中间坐标点对应K线数组的索引index2;
step6:变动前后的索引值相减可以获得变动的柱状图条数,重新渲染图形即可。
这里有几个点需要注意:1. 缩小时左边数据已经到底则需要加载更多数据,2.刻度粗细应该设置上下限在达到上下限的时候避免再次渲染图形。下面是部分计算代码:
// 放大缩小刻度
const scale = (touchDistance - this.nextTouchDistance) / this.nextTouchDistance; // 放大缩小事件
this.zoomIn(centerX, scale);
step1:计算两指中间坐标点
// 两指x轴方向距离
const xLen = Math.abs(
e.targetTouches[1].pageX - e.targetTouches[0].pageX,
);
// 两指y轴方向距离(横屏需要)
const yLen = Math.abs(
e.targetTouches[1].pageY - e.targetTouches[0].pageY,
);
// canvas内容区矩形的边框信息
const clientRect = this.container.getBoundingClientRect(); // 相对于屏幕的中心点
let center;
// 相对于canvas图层的中心点
let centerX; center = e.targetTouches[1].clientX - (e.targetTouches[1].clientX -
e.targetTouches[0].clientX) / 2;
centerX = center - clientRect.left;
step2:计算两指间的直线距离;
// 两指间距离 (根据勾股定理计算)
const touchDistance = Math.sqrt(xLen * xLen + yLen * yLen);
step3:根据直线距离以及上一次的直线距离计算需要放大或缩小的刻度;
// 需要放大缩小刻度
const scale = (touchDistance - this.nextTouchDistance) / this.nextTouchDistance; // 放大缩小处理
this.zoomIn(centerX, scale); // 记住本次两指间距离
this.nextTouchDistance = touchDistance;
step4:计算刻度不变时中间坐标点对应K线数组的索引index1;step5:计算刻度变动后中间坐标点对应K线数组的索引index2;
step6:变动前后的索引值相减可以获得变动的柱状图条数,重新渲染图形即可。
zoomIn(centerX, scale) {
// 中心刻度
const centerScale = Math.ceil(centerX / this.cellWidth); // this.cellWidth为图中柱状图宽度
// K线数组索引(刻度不变时中间坐标点)
const centerIndex = Math.max(Math.min(this.kData.length - 1, centerScale - 1), 0);
const originalScale = this.scale;
this.scale += scale;
if (this.scale <= 0.5) {
this.scale = 0.5;
} else if (this.scale > 4) {
this.scale = 4;
}
if (originalScale !== this.scale) {
// K线条目数
this.count = Math.floor(60 / this.scale)
this.cellWidth = this.canvasWidth / this.count;
// 计算刻度变动后中间坐标点
const centerScale1 = Math.ceil(centerX / this.cellWidth );
const centerIndex1 = Math.max(Math.min(this.kData.length - 1, centerScale1 - 1),0);
const scaleDiff = centerIndex1 - centerIndex;
let index = this.indexStart - scaleDiff;
index = Math.min(index, Math.max(this.allData.length - this.count, 0));
index = Math.max(index, 0);
if (index === 0 && this.loadMore === false) {
this.loadMore = true;
this.loadMoreCallback();
return;
}
this.indexStart = index;
this.indexStartTemp = index;
const data = this.allData.slice(index, index + this.count) || [];
// 重新渲染图形
this.drawKLine();
}
},
以上代码有些方法没有体现出来,因为代码比较长所以只粘贴相应的代码,如果有迷惑的地方,可以下方留言。
3、惯性滑动事件:
惯性滑动在移动端是个很好的体验,什么时候会触发惯性呢,两次滑动的间隔时间小于一个设定值既可触发惯性滑动。惯性需要考虑加速度,灵敏度等因素。这里惯性是用的js中requestAnimationFrame方法,存在兼容性问题可以用setTimeout模拟这里不多做兼容处理的介绍,因为大部分机型是兼容的。具体实现为:在滑动开始时记录时间,在触摸结束事件中判断时间间隔是否小于100ms,如果小于100ms则执行惯性滑动事件,根据滑动最后时间和滑动开始时间计算滑动速度,然后根据设置的灵敏度来计算加速度,执行惯性动画,然后每执行一次惯性事件减少速度直到速度为0停止惯性事件。以下为部分代码:
// 触摸结束事件
touchendEvent(e) {
this.touchEndTime = new Date().getTime();
// 开始移动事件和触摸结束事件时间间隔
const intervalTime = this.touchEndTime - this.startMoveTime;
// 最后一次滑动结束和开始滑动时间间隔
let timeStamp = this.endMoveTime - this.startMoveTime;
timeStamp = timeStamp > 0 ? timeStamp : 8;
// 停顿时间超过100ms不产生惯性滑动;
if (intervalTime < 100) {
this.speed = Math.abs((this.startX - this.currentX) / timeStamp); // 计算速度
this.acceleration = this.speed / this.sensitivity; // 根据灵敏度(sensitivity)计算加速度
this.frameStartTime = new Date().getTime(); // 动画开始时间
this.inertiaFrame = requestAnimationFrame(this.moveByInertia);
}
} // 惯性滑动时间
moveByInertia() {
this.frameEndTime = new Date().getTime(); // 每次动画结束时间
this.frameTime = this.frameEndTime - this.frameStartTime; // 动画执行时间
if (this.currentX < this.startX) {
// 向左惯性滑动;
this.roll.dir = 1;
} else {
// 向右惯性滑动;
this.roll.dir = -1;
}
this.speed = Math.max(this.speed - this.acceleration * this.frameTime, 0); // 逐渐降低速度
if (this.speed === 0) {
cancelAnimationFrame(this.inertiaFrame);
this.touchEnd();
return;
}
this.roll.len += this.speed;
this.swipe(this.roll); // 执行滑动K线方法
this.frameStartTime = this.frameEndTime;
this.inertiaFrame = requestAnimationFrame(this.moveByInertia);
}
这里也有几点代码中没有写进去的如需要判断是否已经触及边缘、横屏的处理等等。
3、触底回弹事件:
触底回弹主要是需要判断是否已经到左右两侧的点,设置到达临界点后允许滑动的K线条数结束滑动后进行惯性回弹至临界点,惯性回弹类似惯性滑动的处理。以下为主要逻辑代码:
// 惯性回弹
springbackByInertia() {
// 设置到达临界点后允许滑动K线条数为5根
const roll = { dir: 1, len: 5 };
// indexStartTemp是k线数组开始的标识位
// 如果this.indexStartTemp > this.allData.length - this.count则判断到达了最左侧的临界点
// 如果this.indexStartTemp < 0则判断达到了最右侧的境界点
if (
this.indexStartTemp > this.allData.length - this.count ||
this.indexStartTemp < 0
) {
this.swipe(roll);
this.springInertiaFrame = requestAnimationFrame(this.springbackByInertia);
} else {
cancelAnimationFrame(this.springInertiaFrame);
this.swipe(roll);
}
},
4、横屏事件:
这里处理横屏事件是通过轻击事件来触发的,如何判断是否为轻击事件呢。在触摸结束事件中判断触摸时间小于100ms且移动距离小于5px即视为轻击事件来触发横屏事件。下面为判断轻击事件的代码:
// 是否为轻击事件(触摸时间小于100ms且移动距离小于5px)
isTap() {
if (this.touchEndTime - this.touchStartTime < 100) {
if (!this.currentX && !this.currentY) {
return true;
}
const xScale = Math.abs(this.currentX - this.startX);
const yScale = Math.abs(this.currentY - this.startY);
if (xScale < 5 && yScale < 5) {
return true;
}
}
return false;
},
横屏的实现这里是利用css3的旋转属性对canvas进行90度旋转,旋转后需要注意的是滑动的时候X轴与Y轴要和竖屏的时候替换来处理。
三,结言
以上是我们自研行情的K线图部分的处理,难免有些地方存在不足,也希望读者能给予意见和指导。由于以上代码大都为代码片段,所以会存在变量或方法名没有定义的情况,望多谅解。我们的行情推送是使用websocket推送结合protobuf数据格式来完成的,如果有需要可以另外介绍。
注:本文和CSDN文章h5行情k线开发为同一作者
*转载请附出处。
h5行情k线开发的更多相关文章
- Highcharts candlestick(K线图)案例
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- BotVS开发基础—2.1 账户、行情、K线、深度
代码 import json def main(): Log("账号信息:", exchange.GetAccount()); # Log("K 线数据:", ...
- 高仿MT4行情终端(K线图+操控+简单架构)
技术:VS2015 Update3 + QT 5.11.2 + BOOST 1.68 + QT VS Tools + C++11 概述 模仿外汇MT4的界面 详细 代码下载:http://www. ...
- vue使用tradingview开发K线图相关问题
vue使用tradingview开发K线图相关问题 1.TradingView中文开发文档https://b.aitrade.ga/books/tradingview/CHANGE-LOG.html2 ...
- canvas绘图,html5 k线图,股票行情图
canvas绘图,html5 k线图,股票行情图 canvas跟其他标签一样,也可以通过css来定义样式.但这里需要注意的是:canvas的默认宽高为300px * 150px,在css中为canva ...
- IDEA Plugin,写一个看股票指数和K线的插件
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 没招了,不写点刺激的,你总是不好好看! 以前,我不懂.写的技术就是技术内容,写的场景 ...
- 3. K线基础知识三
1. 阴线 证券市场上指开盘价高于收盘价的K线,K线图上一般用淡蓝色标注,表示股价下跌,当收盘价低于开盘价,也就是股价走势呈下降趋势时,我们称这种形态的K线为阴线. 中间部分实体为蓝色,此时,上影线的 ...
- 2. K线学习知识二
1. K线 - 阳线 定义:阳线是证券市场上指收盘价高于开盘价的K线,K线图中用红线标注表示涨势. A:小阳星 全日中股价波动很小,开盘价与收盘价极其接近,收盘价略高于开盘价. 小阳星的出现,表明行情 ...
- 1. K线基础知识一
1. 什么是K线: K线起源于日本米市交易,它的基本用途就是为了寻找"买卖点". 2. K线按照计算周期可分为日K线,周K线,月K线,年K线. 周K线:周一的开盘价,周五的收盘价, ...
随机推荐
- HDU 1263 水果 结构体排序
解题报告:一个结构体排序的题,用了一个运算符重载,要注意的是不同的地方可能会产相同的水果,一开始没注意. #include<cstdio> #include<cstring> ...
- 字符串hash&&对字符串hash的理解
对字符串hash的一些总结: 1,首先,我们在转化的时候,取底的时候一般是取131这些数,因为要避免不同的字符串对应相同的hash值这种情况的出现.如果卡精度的时候,我们可以采取双模数的方式尽量减少 ...
- IIS8.0 配置应用程序初始化功能
IIS进程回收后,第一次访问会超级慢,这对于用户是不能接受的,怎么解决这个问题? 我们不能设置IIS不回收进程,因为这样可能会导致IIS内存泄漏.有效的方法时,尽量在业务空闲时间回收进程,回收后立刻预 ...
- ubuntu16.04 eclipse+pydev 配置
参考:http://blog.csdn.net/bluish_white/article/details/56509446,http://blog.csdn.net/qing101hua/articl ...
- Linux下C程序的反汇编【转】
转自:http://blog.csdn.net/u011192270/article/details/50224267 前言:本文主要介绍几种反汇编的方法. gcc gcc的完整编译过程大致为:预处理 ...
- 使用Netcat进行攻击
https://www.freebuf.com/column/135007.html 在网上找到了一个开启了ftp服务的服务: http://static.vhdong.com/Upload/Temp ...
- souce insight出错 There was an error opening project
souce insight出错 There was an error opening project: "...": Options->Preferences->Fol ...
- Mac上删除不了的文件,Windows上也粉碎不了怎么办?
推荐一个Mac上的软件:Tuxera Disk Manager 用法很简单:选择删除文件原来所在的文件进行维护就可以了. 维护之后,在废纸篓中清除,成功.
- No.17 selenium学习之路之判断与等待
一.三种等待方式 1.sleep 加载time库.time.sleep() 休眠单位以秒为单位 2.implicitly_wait() 等待页面完全加载完成(左上角转圈结束) 参数为等待时间,等待页面 ...
- linux下快速安装emacs方法
背景 在公司工作的时候经常需要在很多服务器之间切换,而公司的服务器上一般都没emacs,因此总结一下快速安装emacs的方法. 最简单的是直接使用yum安装,但是有两个问题,一个是有的生产服务器直接没 ...