前言

        由于公司项目需要,要做港股行情的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();
}
}
2、放大缩小事件:

这个事件是双指事件,在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线开发的更多相关文章

  1. Highcharts candlestick(K线图)案例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  2. BotVS开发基础—2.1 账户、行情、K线、深度

    代码 import json def main(): Log("账号信息:", exchange.GetAccount()); # Log("K 线数据:", ...

  3. 高仿MT4行情终端(K线图+操控+简单架构)

    技术:VS2015 Update3 + QT 5.11.2 + BOOST 1.68 + QT VS Tools + C++11   概述 模仿外汇MT4的界面 详细 代码下载:http://www. ...

  4. vue使用tradingview开发K线图相关问题

    vue使用tradingview开发K线图相关问题 1.TradingView中文开发文档https://b.aitrade.ga/books/tradingview/CHANGE-LOG.html2 ...

  5. canvas绘图,html5 k线图,股票行情图

    canvas绘图,html5 k线图,股票行情图 canvas跟其他标签一样,也可以通过css来定义样式.但这里需要注意的是:canvas的默认宽高为300px * 150px,在css中为canva ...

  6. IDEA Plugin,写一个看股票指数和K线的插件

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 没招了,不写点刺激的,你总是不好好看! 以前,我不懂.写的技术就是技术内容,写的场景 ...

  7. 3. K线基础知识三

    1. 阴线 证券市场上指开盘价高于收盘价的K线,K线图上一般用淡蓝色标注,表示股价下跌,当收盘价低于开盘价,也就是股价走势呈下降趋势时,我们称这种形态的K线为阴线. 中间部分实体为蓝色,此时,上影线的 ...

  8. 2. K线学习知识二

    1. K线 - 阳线 定义:阳线是证券市场上指收盘价高于开盘价的K线,K线图中用红线标注表示涨势. A:小阳星 全日中股价波动很小,开盘价与收盘价极其接近,收盘价略高于开盘价. 小阳星的出现,表明行情 ...

  9. 1. K线基础知识一

    1. 什么是K线: K线起源于日本米市交易,它的基本用途就是为了寻找"买卖点". 2. K线按照计算周期可分为日K线,周K线,月K线,年K线. 周K线:周一的开盘价,周五的收盘价, ...

随机推荐

  1. Java并发编程原理与实战十二:深入理解volatile原理与使用

    volatile:称之为轻量级锁,被volatile修饰的变量,在线程之间是可见的. 可见:一个线程修改了这个变量的值,在另一个线程中能够读取到这个修改后的值. synchronized除了线程之间互 ...

  2. spring-boot添加自定义拦截器

    spring-boot中的WebMvcConfigurerAdapter类提供了很多自定义操作的方法,先贴出来大家看看 package org.springframework.web.servlet. ...

  3. 2017萧山第5场(2016 Pacific Northwest - Division 1)

    B:Buggy Robot [题意] 一个n*m的地图(1≤n, m≤50),有一个入口和一个出口.给定一个命令序列(上,下,左,右),如果碰到障碍或者边际就忽略.问至少加入或删除多少个的命令,使得能 ...

  4. pentaho bi server 配置MySQL数据库

    软件版本: jdk 1.7 MySQL 5.5 biserver-ce-6.1.0.1-196 (选择右下方的所有选项See All Activities) 一.前置环境安装 1.安装jdk(略) 2 ...

  5. python 进程 线程 协程

    并发与并行:并行是指两个或者多个事件在同一时刻发生:而并发是指两个或多个事件在同一时间间隔内发生.在单核CPU下的多线程其实都只是并发,不是并行. 进程是系统资源分配的最小单位,进程的出现是为了更好的 ...

  6. PHP返回Json数据函数封装

    /** * 返回Json数据 * @param int $code * @param string $message * @param array $data * @return string */ ...

  7. CSS overscroll-behavior

    overscroll-behavior新属性解决了在手机上弹出滚动的一些问题,具体内容查看网址:https://www.w3cplus.com/css/overscroll-behavior.html

  8. 编写shell脚本一键启动zookeeper集群!!

    踩了一个多小时坑终于解决了: 这里分享给大家,更主要的目的是记住这些坑,避免以后重复走!!! 首先,这里采用ssh秘钥方式进行集群主机之间免密登录执行启动命令 这里简单说下原理: 通过ssh去另外一台 ...

  9. 内核定时器的使用(好几个例子add_timer)【转】

    转自:http://blog.csdn.net/jidonghui/article/details/7449546 LINUX内核定时器是内核用来控制在未来某个时间点(基于jiffies)调度执行某个 ...

  10. 【2017-10-1】雅礼集训day1

    今天的题是ysy的,ysy好呆萌啊. A: 就是把一个点的两个坐标看成差分一样的东西,以此作为区间端点,然后如果点有边->区间没有交. B: cf原题啊.....均摊分析,简单的那种. 线段树随 ...