├── .babelrc ├── .editorconfig ├── .gitignore ├── KLine.js ├── README.md ├── index.html ├── package.json ├── server.js ├── src ├── Depth.js ├── KLine.js ├── draw.js ├── drawCsi.js ├── index.js ├── operation.js ├── select.js ├── setData.js └── update.js ├── src2 ├── Depth.js ├── Depth2.js ├── KLine.js ├── canDraw.js ├── computAxis.js ├── draw.js ├── drawAid.js ├── drawMain.js ├── drawTimeline.js ├── index.js ├── operation.js ├── range.js ├── select.js ├── setData.js ├── setOption.js └── tools │ ├── Arrow.js │ ├── Beam.js │ ├── HorizontalBeam.js │ ├── HorizontalLine.js │ ├── Line.js │ ├── ParallelSegment.js │ ├── PriceLine.js │ ├── Segment.js │ ├── VerticalLine.js │ ├── drawLineCache.js │ └── drawLines.js ├── webpack-build.config.js └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0"], 3 | "plugins": ["transform-runtime"] 4 | } 5 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist 4 | *.log 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Deprecated 2 | 3 | 已废弃。 4 | [zkline](https://github.com/zzcwoshizz/zkline) 5 | 6 | # Kline 7 | 8 | k 线插件 9 | 10 | ## Usage 11 | 12 | $ npm install 13 | 14 | $ npm start 15 | 16 | $ npm run server 17 | 18 | [Visit http://localhost:8080](http://localhost:8080) 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 34 | 35 | 36 |
37 |
38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "z-kline", 3 | "version": "2.3.2", 4 | "description": "k线,分时图,深度图", 5 | "main": "KLine.js", 6 | "scripts": { 7 | "start": "webpack-dev-server", 8 | "build": "webpack --config ./webpack-build.config.js", 9 | "server": "node server.js" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "git+https://github.com/zzcwoshizz/z-kline.git" 14 | }, 15 | "keywords": [ 16 | "k线,分时图,深度图,canvas" 17 | ], 18 | "author": "Zhichao Zhang ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/zzcwoshizz/z-kline/issues" 22 | }, 23 | "homepage": "https://github.com/zzcwoshizz/z-kline#readme", 24 | "devDependencies": { 25 | "babel-core": "^6.24.1", 26 | "babel-eslint": "^7.2.1", 27 | "babel-loader": "^6.4.1", 28 | "babel-plugin-transform-runtime": "^6.23.0", 29 | "babel-polyfill": "^6.23.0", 30 | "babel-preset-es2015": "^6.24.0", 31 | "babel-preset-stage-0": "^6.22.0", 32 | "babel-register": "^6.24.0", 33 | "babel-runtime": "^6.23.0", 34 | "cors": "^2.8.3", 35 | "cross-env": "^3.2.4", 36 | "es": "^0.5.2", 37 | "eslint": "^3.18.0", 38 | "eslint-loader": "^1.7.0", 39 | "eslint-plugin-babel": "^4.1.1", 40 | "express": "^4.15.2", 41 | "request": "^2.81.0", 42 | "webpack": "^2.3.2", 43 | "webpack-dev-server": "^2.4.2" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const cors = require('cors'); 3 | const request = require('request'); 4 | const WebSocket = require('ws'); 5 | 6 | // let ws = new WebSocket('ws://192.168.16.49:8080/infoCenter/btc', [], { 7 | // perMessageDeflate: true 8 | // }); 9 | // ws.on('open', function() { 10 | // ws.send('["market:add","btctrade:btc"]'); 11 | // ws.on('message', function(data) { 12 | // console.log(data); 13 | // }); 14 | // }); 15 | 16 | const app = express(); 17 | app.use(cors({ 18 | origin: ['http://localhost:8080', 'http://192.168.16.160:8080'], 19 | methods: ['GET', 'PUT', 'POST', 'DELETE'], 20 | allowedHeaders: ['Content-Type', 'Authorization', 'token'], 21 | credentials: true, 22 | })); 23 | 24 | app.post('/user', function(req, res) { 25 | res.json({ success: true }); 26 | }); 27 | 28 | app.get('/data', function(req, res) { 29 | request({ 30 | url: 'https://k.sosobtc.com/data/period?symbol=okcoinbtccny&step=60', 31 | method: 'GET', 32 | headers: { 33 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36', 34 | 'Content-Type': 'application/json', 35 | }, 36 | }, function (erro, response, body) { 37 | res.json(JSON.parse(body)); 38 | }); 39 | }); 40 | 41 | 42 | const io = require('socket.io').listen(app.listen(3000)); 43 | let wurl = 'wss://io.sosobtc.com/socket.io/?EIO=3&transport=websocket'; 44 | let ws = new WebSocket(wurl, [], { 45 | perMessageDeflate: true 46 | }); 47 | ws.on('open', function() { 48 | ws.send('420["market.subscribe","btc:okcoin"]'); 49 | setInterval(function() { 50 | ws.send('3'); 51 | }, 3600); 52 | ws.on('message', function(data, flags) { 53 | if (data.indexOf('update:trades') > -1) { 54 | console.log(JSON.parse(data.slice(2, data.length))[1][0]); 55 | io.emit('update', JSON.parse(data.slice(2, data.length))[1][0]); 56 | } 57 | if (data.indexOf('update:depth') > -1) { 58 | console.log(JSON.parse(data.slice(2, data.length))[1]); 59 | io.emit('depth', JSON.parse(data.slice(2, data.length))[1]); 60 | } 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /src/Depth.js: -------------------------------------------------------------------------------- 1 | export default function Depth(ele, option) { 2 | this.dpr = window.devicePixelRatio > 2 ? window.devicePixelRatio : 2; 3 | ele.style.width = option.width + 'px'; 4 | ele.style.height = option.height + 'px'; 5 | 6 | let canvas = document.createElement('canvas'); 7 | canvas.style.width = option.width + 'px'; 8 | canvas.style.height = option.height + 'px'; 9 | canvas.width = option.width * this.dpr; 10 | canvas.height = option.height * this.dpr; 11 | this.canvas = canvas; 12 | 13 | this.width = option.width * this.dpr; 14 | this.height = option.height * this.dpr; 15 | this.ctx = canvas.getContext('2d'); 16 | this.ctx.font = this.dpr * 12 + 'px sans-serif'; 17 | 18 | ele.appendChild(canvas); 19 | 20 | this.theme = option.theme || 'white'; 21 | this.colors = { 22 | background: this.theme === 'dark' ? 'black' : 'white', 23 | fontColor: this.theme === 'dark' ? '#656565' : '#656565', 24 | splitColor: this.theme === 'dark' ? '#333' : '#ccc', 25 | }; 26 | 27 | canvas.addEventListener('mousemove', e => { 28 | this.pos = this.getMousePos(e); 29 | this.setData(); 30 | }); 31 | canvas.addEventListener('mouseout', e => { 32 | this.pos = null; 33 | this.setData(); 34 | }); 35 | canvas.addEventListener('mousecancel', e => { 36 | this.pos = null; 37 | this.setData(); 38 | }); 39 | } 40 | 41 | Depth.prototype.setData = function(data) { 42 | if (!data) { 43 | data = this.data; 44 | } else { 45 | this.data = data; 46 | } 47 | const { buy, sell } = this.data; 48 | 49 | const buyPrice = []; 50 | const sellPrice = []; 51 | const buyVolume = []; 52 | const sellVolume = []; 53 | buy.forEach(el => { 54 | buyPrice.push(el[0]); 55 | buyVolume.push(el[1]); 56 | }); 57 | sell.forEach(el => { 58 | sellPrice.push(el[0]); 59 | sellVolume.push(el[1]); 60 | }); 61 | let buyDepth = []; 62 | for (let i = 0; i < buyVolume.length; i++) { 63 | if (i === 0) { 64 | buyDepth[i] = parseFloat(buyVolume[i]); 65 | continue; 66 | } 67 | buyDepth[i] = buyDepth[i - 1] + parseFloat(buyVolume[i]); 68 | } 69 | let sellDepth = []; 70 | for (let i = 0; i < sellVolume.length; i++) { 71 | if (i === 0) { 72 | sellDepth[i] = parseFloat(sellVolume[i]); 73 | continue; 74 | } 75 | sellDepth[i] = sellDepth[i - 1] + parseFloat(sellVolume[i]); 76 | } 77 | 78 | const maxVolume = Math.max(buyDepth[buyDepth.length - 1], sellDepth[sellDepth.length - 1]); 79 | let n = (maxVolume * 0.2).toFixed(0).length; 80 | const interval = Math.ceil(maxVolume * 0.2 / Math.pow(10, n - 1)) * Math.pow(10, n - 1); 81 | const yAxis = []; 82 | for (let i = interval; i < maxVolume; i += interval) { 83 | yAxis.unshift(i); 84 | } 85 | 86 | let ctx = this.ctx; 87 | ctx.clearRect(0, 0, this.width, this.height); 88 | ctx.fillStyle = this.colors.background; 89 | ctx.fillRect(0, 0, this.width, this.height); 90 | 91 | 92 | let maxLength = 0; 93 | for (let i = interval; i < maxVolume; i += interval) { 94 | maxLength = Math.max(maxLength, ctx.measureText(i.toString()).width); 95 | } 96 | this.contentWidth = this.width - maxLength - 10; 97 | this.contentHeight = this.height - this.dpr * 16; 98 | 99 | 100 | n = ((sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]) * 0.25).toFixed(0).length; 101 | const intervalX = Math.ceil(maxVolume * 0.25 / Math.pow(10, n - 1)) * Math.pow(10, n - 1); 102 | ctx.fillStyle = this.colors.fontColor; 103 | ctx.textAlign = 'center'; 104 | ctx.textBaseline = 'top'; 105 | for (let i = buyPrice[buyPrice.length - 1] + intervalX; i < sellPrice[sellPrice.length - 1]; i += intervalX) { 106 | ctx.fillText(parseInt(i), (i - buyPrice[buyPrice.length - 1]) / (sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]) * this.contentWidth, this.contentHeight); 107 | } 108 | 109 | ctx.textAlign = 'left'; 110 | ctx.textBaseline = 'middle'; 111 | ctx.fillStyle = this.colors.fontColor; 112 | ctx.save(); 113 | ctx.strokeStyle = this.colors.splitColor; 114 | ctx.setLineDash([2, 2]); 115 | for (let i = interval; i < maxVolume; i += interval) { 116 | let y = this.contentHeight - this.contentHeight * i / maxVolume; 117 | ctx.fillText(i, this.contentWidth + 5, y); 118 | ctx.beginPath(); 119 | ctx.moveTo(0, y); 120 | ctx.lineTo(this.contentWidth, y); 121 | ctx.stroke(); 122 | } 123 | ctx.restore(); 124 | 125 | // 买单 126 | const p1 = (buyPrice[0] - buyPrice[buyPrice.length - 1]) / (sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]); 127 | ctx.lineWidth = this.dpr; 128 | ctx.beginPath(); 129 | ctx.moveTo(0, this.contentHeight - buyDepth[buyDepth.length - 1] / maxVolume * this.contentHeight); 130 | for (let i = buyDepth.length - 2; i >= 0; i--) { 131 | ctx.lineTo(this.contentWidth * p1 - this.contentWidth * p1 * i / buyDepth.length, this.contentHeight - buyDepth[i] / maxVolume * this.contentHeight); 132 | if (i === 0) { 133 | ctx.lineTo(this.contentWidth * p1 - this.contentWidth * p1 * i / buyDepth.length, this.contentHeight); 134 | } 135 | } 136 | ctx.lineTo(0, this.contentHeight); 137 | ctx.closePath(); 138 | let lineargradient = ctx.createLinearGradient(0, 0, 0, this.contentHeight); 139 | lineargradient.addColorStop(0, 'rgba(54, 168, 83, 1)'); 140 | lineargradient.addColorStop(1, 'rgba(171, 205, 82, 0.2)'); 141 | ctx.fillStyle = lineargradient; 142 | ctx.fill(); 143 | ctx.strokeStyle = '#246b38'; 144 | ctx.stroke(); 145 | 146 | // 卖单 147 | const p2 = (sellPrice[sellPrice.length - 1] - sellPrice[0]) / (sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]); 148 | ctx.beginPath(); 149 | ctx.moveTo(this.contentWidth, this.contentHeight - sellDepth[sellDepth.length - 1] / maxVolume * this.contentHeight); 150 | for (let i = sellDepth.length - 2; i >= 0; i--) { 151 | ctx.lineTo(this.contentWidth * (1 - p2) + this.contentWidth * p2 * i / sellDepth.length, this.contentHeight - sellDepth[i] / maxVolume * this.contentHeight); 152 | if (i === 0) { 153 | ctx.lineTo(this.contentWidth * (1 - p2) + this.contentWidth * p2 * i / sellDepth.length, this.contentHeight); 154 | } 155 | } 156 | ctx.lineTo(this.contentWidth, this.contentHeight); 157 | ctx.closePath(); 158 | lineargradient = ctx.createLinearGradient(this.contentWidth, 0, this.contentWidth, this.contentHeight); 159 | lineargradient.addColorStop(0, 'rgba(216, 34, 13, 1)'); 160 | lineargradient.addColorStop(1, 'rgba(233, 84, 21, 0.2)'); 161 | ctx.fillStyle = lineargradient; 162 | ctx.fill(); 163 | ctx.strokeStyle = '#b81c0b'; 164 | ctx.stroke(); 165 | 166 | ctx.strokeStyle = this.colors.fontColor; 167 | ctx.strokeRect(0, 0, this.contentWidth, this.contentHeight); 168 | 169 | if (this.pos && this.pos.x < this.contentWidth && this.pos.y < this.contentHeight) { 170 | const num = buy.length * 2; 171 | const currentIndex = parseInt(this.pos.x / this.contentWidth * num); 172 | let x; 173 | let y; 174 | let rectH = 90; 175 | let text; 176 | if (currentIndex >= buy.length) { 177 | let i = currentIndex - buy.length; 178 | text = [sell[i][0], sellDepth[i]]; 179 | ctx.beginPath(); 180 | x = this.contentWidth * (1 - p2) + this.contentWidth * p2 * i / sellDepth.length; 181 | y = this.contentHeight - sellDepth[i] / maxVolume * this.contentHeight; 182 | ctx.arc(x, y, 8, 0, Math.PI * 2, true); 183 | ctx.closePath(); 184 | ctx.fillStyle = 'rgb(255, 0, 0)'; 185 | ctx.fill(); 186 | ctx.strokeStyle = 'white'; 187 | ctx.stroke(); 188 | } else { 189 | let i = currentIndex; 190 | text = [buy[buy.length - 1 - i][0], buyDepth[buyDepth.length - 1 - i]]; 191 | ctx.beginPath(); 192 | x = this.contentWidth * p1 * i / buyDepth.length; 193 | y = this.contentHeight - buyDepth[buyDepth.length - 1 - i] / maxVolume * this.contentHeight; 194 | ctx.arc(x, y, 8, 0, Math.PI * 2, true); 195 | ctx.closePath(); 196 | ctx.fillStyle = 'rgb(0, 255, 0)'; 197 | ctx.fill(); 198 | ctx.strokeStyle = 'white'; 199 | ctx.stroke(); 200 | } 201 | ctx.strokeStyle = 'white'; 202 | 203 | let rectW = ctx.measureText('买单:' + this.setDP(text[1])).width + 30; 204 | x = x > this.contentWidth * 0.5 ? x - 10 : x + 10; 205 | y = y > this.contentHeight * 0.5 ? y - 10 : y + 10; 206 | rectW = x > this.contentWidth * 0.5 ? -rectW : rectW; 207 | rectH = y > this.contentHeight * 0.5 ? -rectH : rectH; 208 | ctx.save(); 209 | ctx.shadowColor = this.colors.fontColor; 210 | ctx.shadowOffsetX = 0; 211 | ctx.shadowOffsetY = 0; 212 | ctx.shadowBlur = 3; 213 | ctx.strokeRect(x, y, rectW, rectH); 214 | ctx.restore(); 215 | 216 | ctx.fillStyle = '#656565'; 217 | ctx.textAlign = 'left'; 218 | ctx.textBaseline = 'middle'; 219 | let textX = x > this.contentWidth * 0.5 ? x + rectW + 10 : x + 10; 220 | let textY = y > this.contentHeight * 0.5 ? y + rectH * 2 / 3 : y + rectH / 3; 221 | ctx.fillText('¥' + text[0], textX, textY); 222 | textY = y > this.contentHeight * 0.5 ? y + rectH / 3 : y + rectH * 2 / 3; 223 | ctx.fillText((currentIndex < buy.length ? '买单:' : '卖单:') + this.setDP(text[1]), textX, textY); 224 | } 225 | }; 226 | -------------------------------------------------------------------------------- /src/KLine.js: -------------------------------------------------------------------------------- 1 | import setData from './setData'; 2 | import draw from './draw'; 3 | import operation from './operation'; 4 | import select from './select'; 5 | import drawCsi from './drawCsi'; 6 | import update from './update'; 7 | import Depth from './Depth'; 8 | function KLine(ele, option) { 9 | if (option === undefined || option === null) { 10 | option = {}; 11 | } 12 | this.ele = ele; 13 | this.setOption(option); 14 | this.operation(); 15 | } 16 | 17 | KLine.prototype = { 18 | setOption, 19 | init, 20 | operation, 21 | setData, 22 | draw, 23 | drawCsi, 24 | select, 25 | setDP, 26 | getMousePos, 27 | isInLineView, 28 | update, 29 | }; 30 | 31 | // 获取鼠标在canvas上的坐标点 32 | function getMousePos(e) { 33 | let rect = e.target.getBoundingClientRect(); 34 | return { 35 | x: (e.clientX - rect.left) * this.dpr, 36 | y: (e.clientY - rect.top) * this.dpr 37 | }; 38 | } 39 | 40 | // 判断鼠标是否在${this.views}中 41 | function isInLineView(pos) { 42 | const { x, y } = pos; 43 | const view1 = this.views[0]; 44 | const view2 = this.views[2]; 45 | if (x >= view1.x && x < view1.x + view1.w && y >= view1.y && y < view1.y + view1.h) { 46 | return 0; 47 | } else if (x >= view2.x && x < view2.x + view2.w && y >= view2.y && y < view2.y + view2.h) { 48 | return 1; 49 | } else { 50 | return false; 51 | } 52 | } 53 | 54 | // 控制小数位数 55 | function setDP(num) { 56 | return Math.abs(num) > 1 ? Number(num.toFixed(2)) : Number(num.toFixed(7)); 57 | } 58 | 59 | function setOption(option) { 60 | // 配置项 61 | if (this.option) { 62 | this.option = { 63 | theme: option.theme || this.option.theme, 64 | width: option.width || this.option.width, 65 | height: option.height || this.option.height, 66 | yAxisWidth: option.yAxisWidth || this.option.yAxisWidth, 67 | fontSize: option.fontSize || this.option.fontSize, 68 | csi: option.csi || this.option.csi, 69 | csi2: option.csi2 || this.option.csi2, 70 | timeFilter: option.timeFilter || this.option.timeFilter, 71 | priceFilter: option.priceFilter || this.option.priceFilter, 72 | overTimeFilter: option.overTimeFilter || this.option.overTimeFilter, 73 | overYFilter: option.overYFilter || this.option.overYFilter, 74 | }; 75 | } else { 76 | this.option = { 77 | theme: option.theme || 'dark', 78 | width: option.width, 79 | height: option.height, 80 | yAxisWidth: option.yAxisWidth || 140, 81 | fontSize: option.fontSize || 12, 82 | csi: option.csi || 'ema', 83 | csi2: option.csi2 || ['volume'], 84 | timeFilter: option.timeFilter || (t => { 85 | return new Date(t * 1000).toLocaleDateString(); 86 | }), 87 | priceFilter: option.priceFilter || (d => { 88 | return Number(d.toFixed(2)); 89 | }), 90 | overTimeFilter: option.overTimeFilter || (t => { 91 | return new Date(t * 1000).toLocaleTimeString(); 92 | }), 93 | overYFilter: option.overYFilter || (d => { 94 | return Number(d.toFixed(2)); 95 | }), 96 | }; 97 | } 98 | this.init(); 99 | } 100 | 101 | function init() { 102 | let flag = true; 103 | this.device = (navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) ? 'mb' : 'pc'; 104 | this.dpr = 2; 105 | const ele = this.ele; 106 | ele.style.fontSize = this.option.fontSize + 'px'; 107 | this.ele = ele; 108 | 109 | var width = this.option.width * this.dpr; 110 | var height = this.option.height * this.dpr; 111 | // canvas宽度 112 | this.width = width; 113 | // canvas高度 114 | this.height = height; 115 | ele.style.width = width / this.dpr + 'px'; 116 | ele.style.height = height / this.dpr + 'px'; 117 | ele.style.position = 'relative'; 118 | 119 | var canvas = this.canvas || document.createElement('canvas'); 120 | if (!this.canvas) { 121 | ele.appendChild(canvas); 122 | flag = false; 123 | } 124 | // 渲染canvas 125 | this.canvas = canvas; 126 | canvas.width = width; 127 | canvas.height = height; 128 | canvas.style.width = '100%'; 129 | canvas.style.height = '100%'; 130 | canvas.style.position = 'absolute'; 131 | 132 | var overCanvas = this.overCanvas || document.createElement('canvas'); 133 | if (!this.overCanvas) { 134 | ele.appendChild(overCanvas); 135 | } 136 | // 覆盖层canvas 137 | this.overCanvas = overCanvas; 138 | overCanvas.width = width; 139 | overCanvas.height = height; 140 | overCanvas.style.width = '100%'; 141 | overCanvas.style.height = '100%'; 142 | overCanvas.style.position = 'absolute'; 143 | 144 | // 渲染上下文对象 145 | const ctx = canvas.getContext('2d'); 146 | this.ctx = ctx; 147 | this.ctx.font = this.option.fontSize * this.dpr + 'px sans-serif'; 148 | // 覆盖层的渲染上下文对象 149 | const overCtx = overCanvas.getContext('2d'); 150 | this.overCtx = overCtx; 151 | this.overCtx.font = this.option.fontSize * this.dpr + 'px sans-serif'; 152 | 153 | // 上下画图区域高度比 154 | if (this.option.csi2.length == 1) { 155 | this.split = [7, 3]; 156 | } else { 157 | this.split = [10, 0]; 158 | } 159 | const yAxisWidth = this.option.yAxisWidth; 160 | 161 | const left = 20; 162 | const right = 20; 163 | const top = 40 * this.dpr; 164 | const bottom = 100; 165 | const middle = 20; 166 | let view1 = { 167 | x: left, 168 | y: top, 169 | w: width - yAxisWidth - left - right - middle, 170 | h: (height - top - bottom) * (this.split[0] / (this.split[0] + this.split[1])) - middle * 0.5, 171 | }; 172 | let view2 = { 173 | x: view1.w + view1.x + middle, 174 | y: view1.y, 175 | w: yAxisWidth, 176 | h: view1.h, 177 | }; 178 | let view3 = { 179 | x: view1.x, 180 | y: view1.y + view1.h + middle, 181 | w: view1.w, 182 | h: (height - top - bottom) * (this.split[1] / (this.split[0] + this.split[1])) + middle * 0.5, 183 | }; 184 | let view4 = { 185 | x: view2.x, 186 | y: view3.y, 187 | w: yAxisWidth, 188 | h: view3.h, 189 | }; 190 | const views = [view1, view2, view3, view4]; 191 | this.views = views; 192 | 193 | // 设置全局色彩 194 | const isDarkTheme = this.option.theme === 'dark'; 195 | this.colors = { 196 | background: isDarkTheme ? '#2e3947' : 'white', 197 | timeBackground: isDarkTheme ? '#343f4d' : '#fff', 198 | splitLine: isDarkTheme ? 'rgb(66, 73, 82)' : '#eee', 199 | subline: isDarkTheme ? 'rgb(86, 93, 102)' : '#ddd', 200 | textColor: isDarkTheme ? '#fff' : '#333', 201 | currentTextColor: isDarkTheme ? 'rgb(239, 229, 46)' : 'rgb(242, 121, 53)', 202 | textFrameColor: isDarkTheme ? 'white' : 'black', 203 | greenColor: isDarkTheme ? '#3bd181' : '#48b484', 204 | redColor: isDarkTheme ? '#eb3f2f' : '#d64541', 205 | ma30Color: isDarkTheme ? 'rgb(234, 177, 103)' : 'rgb(234, 177, 103)', 206 | ma7Color: isDarkTheme ? 'rgb(166, 206, 227)' : 'rgb(59, 187, 59)', 207 | macdColor: isDarkTheme ? 'rgb(208, 146, 209)' : 'rgb(208, 146, 209)', 208 | }; 209 | if (flag) { 210 | this.setData(this.data); 211 | } 212 | } 213 | 214 | export { 215 | KLine, 216 | Depth, 217 | }; 218 | Depth.prototype.getMousePos = getMousePos; 219 | Depth.prototype.setDP = setDP; 220 | -------------------------------------------------------------------------------- /src/draw.js: -------------------------------------------------------------------------------- 1 | let lastStartIndex = -1; 2 | let lastEndIndex = -1; 3 | let lastVerticalRectNumber = -1; 4 | export default function draw(flag) { 5 | if (this.isDraw) { 6 | return; 7 | } 8 | if (lastStartIndex === this.state.startIndex && lastEndIndex === this.state.endIndex && lastVerticalRectNumber === this.state.verticalRectNumber && !flag) { 9 | return; 10 | } 11 | this.isDraw = true; 12 | this.state.yaxis = computAxis.call(this); 13 | this.ctx.clearRect(0, 0, this.width, this.height); 14 | drawBackground.call(this); 15 | drawKLine.call(this); 16 | if (this.option.csi2.length > 0) { 17 | this.drawCsi(); 18 | } 19 | this.isDraw = false; 20 | lastStartIndex = this.state.startIndex; 21 | lastEndIndex = this.state.endIndex; 22 | lastVerticalRectNumber = this.state.verticalRectNumber; 23 | } 24 | 25 | function drawKLine() { 26 | const ctx = this.ctx; 27 | const theme = this.option.theme; 28 | 29 | const times = this.state.times; 30 | const timeStr = this.state.timeStr; 31 | const start = this.state.start; 32 | const hi = this.state.hi; 33 | const lo = this.state.lo; 34 | const close = this.state.close; 35 | 36 | const { 37 | max, 38 | min, 39 | maxPrice, 40 | maxPriceIndex, 41 | minPrice, 42 | minPriceIndex, 43 | intervalY, 44 | } = this.state.yaxis; 45 | 46 | const view1 = this.views[0]; 47 | const view2 = this.views[1]; 48 | const view3 = this.views[2]; 49 | 50 | ctx.fillStyle = this.colors.textColor; 51 | ctx.strokeStyle = this.colors.splitLine; 52 | ctx.lineWidth = this.dpr * 0.5; 53 | ctx.setLineDash([2 * this.dpr], 2 * this.dpr); 54 | ctx.textAlign = 'center'; 55 | ctx.textBaseline = 'middle'; 56 | let lengthY = (max - min) / intervalY; 57 | for (let i = 0; i < lengthY; i++) { 58 | ctx.fillText(this.option.priceFilter(max - (i * intervalY)), view2.x + view2.w * 0.5, i * intervalY / (max - min) * view2.h + view2.y); 59 | 60 | let x = view2.x; 61 | let y = i * intervalY / (max - min) * view2.h + view2.y; 62 | ctx.beginPath(); 63 | ctx.moveTo(0, y); 64 | ctx.lineTo(x, y); 65 | ctx.stroke(); 66 | } 67 | 68 | ctx.lineWidth = this.dpr; 69 | ctx.setLineDash([]); 70 | ctx.strokeStyle = this.colors.textColor; 71 | for (let i = 0; i < lengthY; i++) { 72 | let x = view2.x; 73 | let y = i * intervalY / (max - min) * view2.h + view2.y; 74 | ctx.beginPath(); 75 | ctx.moveTo(x + 10, y); 76 | ctx.lineTo(x, y); 77 | ctx.stroke(); 78 | } 79 | 80 | ctx.textAlign = 'center'; 81 | ctx.textBaseline = 'middle'; 82 | for (let i = 1; i < 5; i++) { 83 | let index = parseInt((i / 5) * this.state.verticalRectNumber + this.state.startIndex); 84 | if (index >= times.length) { 85 | break; 86 | } 87 | let x = view1.x + view1.w * i / 5; 88 | let y = (this.height + view3.y + view3.h) * 0.5; 89 | ctx.fillText(timeStr[index], x, y); 90 | 91 | ctx.beginPath(); 92 | ctx.moveTo(x, this.height - 2); 93 | ctx.lineTo(x, this.height - 8); 94 | ctx.stroke(); 95 | } 96 | 97 | ctx.strokeStyle = this.colors.redColor; 98 | ctx.fillStyle = this.colors.redColor; 99 | for (let i = this.state.startIndex, j = 0; i < this.state.endIndex; i++, j++) { 100 | if (i >= times.length) { 101 | break; 102 | } 103 | if (close[i] <= start[i]) { 104 | let x = (j + 0.1) * view1.w / this.state.verticalRectNumber + view1.x; 105 | let y = (max - Math.max(start[i], close[i])) / (max - min) * view1.h + view1.y; 106 | let w = view1.w / this.state.verticalRectNumber * 0.8; 107 | let h = (Math.max(start[i], close[i]) - Math.min(start[i], close[i])) / (max - min) * view1.h; 108 | ctx.fillRect(x, y, w, h < this.dpr ? this.dpr : h); 109 | let x1 = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 110 | let y1 = (max - hi[i]) / (max - min) * view1.h + view1.y; 111 | let x2 = x1; 112 | let y2 = (max - lo[i]) / (max - min) * view1.h + view1.y; 113 | ctx.beginPath(); 114 | ctx.moveTo(x1, y1); 115 | ctx.lineTo(x2, y2); 116 | ctx.stroke(); 117 | } 118 | } 119 | ctx.strokeStyle = this.colors.greenColor; 120 | ctx.fillStyle = this.colors.greenColor; 121 | for (let i = this.state.startIndex, j = 0; i < this.state.endIndex; i++, j++) { 122 | if (i >= times.length) { 123 | break; 124 | } 125 | if (close[i] > start[i]) { 126 | let x = (j + 0.1) * view1.w / this.state.verticalRectNumber + view1.x; 127 | let y = (max - Math.max(start[i], close[i])) / (max - min) * view1.h + view1.y; 128 | let w = view1.w / this.state.verticalRectNumber * 0.8; 129 | let h = (Math.max(start[i], close[i]) - Math.min(start[i], close[i])) / (max - min) * view1.h; 130 | ctx.fillRect(x, y, w, h < this.dpr ? this.dpr : h); 131 | let x1 = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 132 | let y1 = (max - hi[i]) / (max - min) * view1.h + view1.y; 133 | let x2 = x1; 134 | let y2 = (max - lo[i]) / (max - min) * view1.h + view1.y; 135 | ctx.beginPath(); 136 | ctx.moveTo(x1, y1); 137 | ctx.lineTo(x2, y2); 138 | ctx.stroke(); 139 | } 140 | } 141 | 142 | if (this.option.csi === 'ma') { 143 | // ma30 144 | ctx.beginPath(); 145 | ctx.strokeStyle = this.colors.ma30Color; 146 | for (let i = this.state.startIndex, j = 0; j < this.state.verticalRectNumber; i++, j++) { 147 | if (i >= this.state.times.length) { 148 | break; 149 | } 150 | let x = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 151 | let y = (max - this.state.ma30[i]) / (max - min) * view1.h + view1.y; 152 | if (j == 0) { 153 | ctx.moveTo(x, y); 154 | } 155 | ctx.lineTo(x, y); 156 | } 157 | ctx.stroke(); 158 | 159 | // ma7 160 | ctx.beginPath(); 161 | ctx.strokeStyle = this.colors.ma7Color; 162 | for (let i = this.state.startIndex, j = 0; j < this.state.verticalRectNumber; i++, j++) { 163 | if (i >= this.state.times.length) { 164 | break; 165 | } 166 | let x = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 167 | let y = (max - this.state.ma7[i]) / (max - min) * view1.h + view1.y; 168 | if (j == 0) { 169 | ctx.moveTo(x, y); 170 | } 171 | ctx.lineTo(x, y); 172 | } 173 | ctx.stroke(); 174 | } else if (this.option.csi === 'ema') { 175 | // ema30 176 | ctx.beginPath(); 177 | ctx.strokeStyle = this.colors.ma30Color; 178 | for (let i = this.state.startIndex, j = 0; j < this.state.verticalRectNumber; i++, j++) { 179 | if (i >= this.state.times.length) { 180 | break; 181 | } 182 | let x = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 183 | let y = (max - this.state.ema30[i]) / (max - min) * view1.h + view1.y; 184 | if (j == 0) { 185 | ctx.moveTo(x, y); 186 | } 187 | ctx.lineTo(x, y); 188 | } 189 | ctx.stroke(); 190 | 191 | // ema7 192 | ctx.beginPath(); 193 | ctx.strokeStyle = this.colors.ma7Color; 194 | for (let i = this.state.startIndex, j = 0; j < this.state.verticalRectNumber; i++, j++) { 195 | if (i >= this.state.times.length) { 196 | break; 197 | } 198 | let x = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 199 | let y = (max - this.state.ema7[i]) / (max - min) * view1.h + view1.y; 200 | if (j == 0) { 201 | ctx.moveTo(x, y); 202 | } 203 | ctx.lineTo(x, y); 204 | } 205 | ctx.stroke(); 206 | } 207 | 208 | // 画最高点,最低点 209 | ctx.fillStyle = this.colors.textColor; 210 | ctx.textBaseline = 'middle'; 211 | let index = (maxPriceIndex - this.state.startIndex); 212 | let index1 = (minPriceIndex - this.state.startIndex); 213 | let maxX = view1.w / this.state.verticalRectNumber * 0.5 + (index + 0.1) * view1.w / this.state.verticalRectNumber + view1.x; 214 | let maxY = (max - maxPrice) / (max - min) * view1.h + view1.y; 215 | let minX = view1.w / this.state.verticalRectNumber * 0.5 + (index1 + 0.1) * view1.w / this.state.verticalRectNumber + view1.x; 216 | let minY = (max - minPrice) / (max - min) * view1.h + view1.y; 217 | if (index < this.state.verticalRectNumber * 0.5) { 218 | ctx.textAlign = 'left'; 219 | ctx.fillText(' ← ' + maxPrice, maxX, maxY); 220 | } else { 221 | ctx.textAlign = 'right'; 222 | ctx.fillText(maxPrice + ' → ', maxX, maxY); 223 | } 224 | if (index1 < this.state.verticalRectNumber * 0.5) { 225 | ctx.textAlign = 'left'; 226 | ctx.fillText(' ← ' + minPrice, minX, minY); 227 | } else { 228 | ctx.textAlign = 'right'; 229 | ctx.fillText(minPrice + ' → ', minX, minY); 230 | } 231 | 232 | // 当前价格 233 | ctx.textAlign = 'left'; 234 | ctx.fillStyle = this.colors.currentTextColor; 235 | ctx.fillText(' ← ' + close[close.length - 1], view1.x + view1.w, (max - close[close.length - 1]) / (max - min) * view1.h + view1.y); 236 | } 237 | 238 | function drawBackground() { 239 | const ctx = this.ctx; 240 | const theme = this.option.theme; 241 | ctx.lineWidth = this.dpr; 242 | ctx.fillStyle = this.colors.background; 243 | ctx.fillRect(0, 0, this.width, this.height); 244 | 245 | const marginTop = 0; 246 | // 垂直分割线 247 | ctx.strokeStyle = this.colors.splitLine; 248 | ctx.beginPath(); 249 | ctx.moveTo(this.views[1].x, 0); 250 | ctx.lineTo(this.views[3].x, this.views[3].y + this.views[3].h + marginTop); 251 | ctx.stroke(); 252 | if (theme === 'dark') { 253 | ctx.fillStyle = this.colors.timeBackground; 254 | ctx.fillRect(0, this.views[2].y + marginTop + this.views[2].h, this.width, this.height); 255 | } else { 256 | ctx.beginPath(); 257 | ctx.moveTo(0, this.views[2].y + this.views[2].h + marginTop); 258 | ctx.lineTo(this.views[3].x + this.views[3].w, this.views[2].y + this.views[2].h + marginTop); 259 | ctx.stroke(); 260 | } 261 | 262 | // 画分割线 263 | if (this.option.csi2.length > 0) { 264 | ctx.strokeStyle = this.colors.splitLine; 265 | ctx.beginPath(); 266 | ctx.moveTo(0, (this.views[0].h + this.views[0].y + this.views[2].y) * 0.5); 267 | ctx.lineTo(this.width, (this.views[0].h + this.views[0].y + this.views[2].y) * 0.5); 268 | ctx.stroke(); 269 | } 270 | } 271 | 272 | export function computAxis() { 273 | const start = this.state.start; 274 | const hi = this.state.hi; 275 | const lo = this.state.lo; 276 | const close = this.state.close; 277 | const ma30 = this.state.ma30; 278 | const ma7 = this.state.ma7; 279 | const ema30 = this.state.ema30; 280 | const ema7 = this.state.ema7; 281 | const startIndex = this.state.startIndex; 282 | const endIndex = this.state.endIndex; 283 | let maxY = Math.max(start[startIndex], hi[startIndex], lo[startIndex], close[startIndex], ma30[startIndex], ma7[startIndex], ema30[startIndex], ema7[startIndex]); 284 | let minY = Math.min(start[startIndex], hi[startIndex], lo[startIndex], close[startIndex], ma30[startIndex], ma7[startIndex], ema30[startIndex], ema7[startIndex]); 285 | let maxPrice = Math.max(start[startIndex], hi[startIndex], lo[startIndex], close[startIndex], ma30[startIndex], ma7[startIndex], ema30[startIndex], ema7[startIndex]); 286 | let minPrice = Math.min(start[startIndex], hi[startIndex], lo[startIndex], close[startIndex], ma30[startIndex], ma7[startIndex], ema30[startIndex], ema7[startIndex]); 287 | let maxPriceIndex = startIndex; 288 | let minPriceIndex = startIndex; 289 | for (let i = startIndex; i < endIndex; i++) { 290 | if (i >= this.state.times.length) { 291 | break; 292 | } 293 | let maxVal = Math.max(start[i], hi[i], lo[i], close[i], ma30[i], ma7[i], ema30[i], ema7[i]); 294 | let minVal = Math.min(start[i], hi[i], lo[i], close[i], ma30[i], ma7[i], ema30[i], ema7[i]); 295 | maxY = maxVal > maxY ? maxVal : maxY; 296 | minY = minVal < minY ? minVal : minY; 297 | let maxPriceVal = hi[i]; 298 | let minPriceVal = lo[i]; 299 | if (maxPriceVal > maxPrice) { 300 | maxPriceIndex = i; 301 | maxPrice = maxPriceVal; 302 | } 303 | if (minPriceVal < minPrice) { 304 | minPriceIndex = i; 305 | minPrice = minPriceVal; 306 | } 307 | } 308 | let cha = maxY - minY; 309 | let n = 0; 310 | if (cha >= 1) { 311 | n = cha.toFixed(0).length; 312 | } else { 313 | let str = cha.toString().split('.')[1]; 314 | for (let i = 0; i < str.length; i++) { 315 | if (str.charAt(i) == 0) { 316 | n--; 317 | } 318 | } 319 | } 320 | const intervalY = Math.ceil((maxY - minY) * 0.2 / Math.pow(10, n - 2)) * Math.pow(10, n - 2); 321 | return { 322 | maxY, 323 | minY, 324 | maxPrice, 325 | maxPriceIndex, 326 | minPrice, 327 | minPriceIndex, 328 | max: maxY + intervalY - maxY % intervalY, 329 | min: minY - minY % intervalY, 330 | intervalY, 331 | }; 332 | } 333 | -------------------------------------------------------------------------------- /src/drawCsi.js: -------------------------------------------------------------------------------- 1 | export default function drawCsi() { 2 | const csi = this.option.csi2; 3 | const views = this.views; 4 | let volumeIndex = csi.indexOf('volume'); 5 | if (volumeIndex > -1) { 6 | drawVolume.call(this, views[(volumeIndex + 1) * 2], views[(volumeIndex + 1) * 2 + 1]); 7 | } 8 | let macdIndex = csi.indexOf('macd'); 9 | if (macdIndex > -1) { 10 | drawMacd.call(this, views[(macdIndex + 1) * 2], views[(macdIndex + 1) * 2 + 1]); 11 | } 12 | } 13 | 14 | function drawVolume(view1, view2) { 15 | const ctx = this.ctx; 16 | const theme = this.option.theme; 17 | 18 | const realVolume = []; 19 | const realVolumeMa7 = []; 20 | const realVolumeMa30 = []; 21 | this.state.volume.forEach((el, i) => { 22 | if (i >= this.state.startIndex && i < this.state.endIndex) { 23 | realVolume.push(el); 24 | realVolumeMa7.push(this.state.volumeMa7[i]); 25 | realVolumeMa30.push(this.state.volumeMa30[i]); 26 | } 27 | }); 28 | const maxVolume = Math.max(...realVolume, ...realVolumeMa7, ...realVolumeMa30) * 1.25; 29 | this.csiYAxisSector = [maxVolume, 0]; 30 | let n = 0; 31 | if (maxVolume >= 1) { 32 | n = maxVolume.toFixed(0).length; 33 | } else { 34 | let str = maxVolume.toString().split('.')[1]; 35 | for (let i = 0; i < str.length; i++) { 36 | if (str.charAt[1] == 0) { 37 | n--; 38 | } 39 | } 40 | } 41 | const interval = Math.ceil(maxVolume * 0.25 / Math.pow(10, n - 2)) * Math.pow(10, n - 2); 42 | const yAxis = []; 43 | for (let i = interval; i < maxVolume; i += interval) { 44 | yAxis.unshift(i); 45 | } 46 | 47 | ctx.textAlign = 'center'; 48 | ctx.textBaseline = 'middle'; 49 | ctx.fillStyle = this.colors.textColor; 50 | ctx.setLineDash([2 * this.dpr], 2 * this.dpr); 51 | ctx.strokeStyle = this.colors.splitLine; 52 | ctx.lineWidth = this.dpr * 0.5; 53 | for (let i = 0; i < yAxis.length; i++) { 54 | ctx.fillText(yAxis[i], view2.x + view2.w * 0.5, view2.y + view2.h - yAxis[i] / maxVolume * view2.h); 55 | ctx.beginPath(); 56 | ctx.moveTo(0, view2.y + view2.h - yAxis[i] / maxVolume * view2.h); 57 | ctx.lineTo(view2.x, view2.y + view2.h - yAxis[i] / maxVolume * view2.h); 58 | ctx.stroke(); 59 | } 60 | 61 | ctx.setLineDash([]); 62 | ctx.lineWidth = this.dpr; 63 | ctx.strokeStyle = this.colors.textColor; 64 | for (let i = 0; i < yAxis.length; i++) { 65 | let x = view2.x; 66 | let y = view2.y + view2.h - yAxis[i] / maxVolume * view2.h; 67 | ctx.beginPath(); 68 | ctx.moveTo(x, y); 69 | ctx.lineTo(x + 10, y); 70 | ctx.stroke(); 71 | } 72 | 73 | ctx.fillStyle = this.colors.greenColor; 74 | for (let i = this.state.startIndex, j = 0; i < this.state.endIndex; i++, j++) { 75 | if (i >= this.state.times.length) { 76 | break; 77 | } 78 | if (this.state.start[i] < this.state.close[i]) { 79 | let x = (j + 0.1) * view1.w / this.state.verticalRectNumber + view1.x; 80 | let w = view1.w / this.state.verticalRectNumber * 0.8; 81 | let h = -realVolume[j] / maxVolume * view1.h; 82 | let y = view1.y + view1.h; 83 | ctx.fillRect(x, y, w, h); 84 | } 85 | } 86 | 87 | ctx.fillStyle = this.colors.redColor; 88 | for (let i = this.state.startIndex, j = 0; i < this.state.endIndex; i++, j++) { 89 | if (i >= this.state.times.length) { 90 | break; 91 | } 92 | if (this.state.close[i] <= this.state.start[i]) { 93 | let x = (j + 0.1) * view1.w / this.state.verticalRectNumber + view1.x; 94 | let w = view1.w / this.state.verticalRectNumber * 0.8; 95 | let h = -realVolume[j] / maxVolume * view1.h; 96 | let y = view1.y + view1.h; 97 | ctx.fillRect(x, y, w, h); 98 | } 99 | } 100 | ctx.beginPath(); 101 | for (let i = this.state.startIndex, j = 0; j < this.state.verticalRectNumber; i++, j++) { 102 | if (i >= this.state.times.length) { 103 | break; 104 | } 105 | ctx.strokeStyle = this.colors.ma30Color; 106 | let x = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 107 | let y = (maxVolume - this.state.volumeMa30[i]) / maxVolume * view1.h + view1.y; 108 | if (j == 0) { 109 | ctx.moveTo(x, y); 110 | } 111 | ctx.lineTo(x, y); 112 | } 113 | ctx.stroke(); 114 | 115 | ctx.beginPath(); 116 | for (let i = this.state.startIndex, j = 0; j < this.state.verticalRectNumber; i++, j++) { 117 | if (i >= this.state.times.length) { 118 | break; 119 | } 120 | ctx.strokeStyle = this.colors.ma7Color; 121 | let x = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 122 | let y = (maxVolume - this.state.volumeMa7[i]) / maxVolume * view1.h + view1.y; 123 | if (j == 0) { 124 | ctx.moveTo(x, y); 125 | } 126 | ctx.lineTo(x, y); 127 | } 128 | ctx.stroke(); 129 | ctx.closePath(); 130 | 131 | } 132 | 133 | function drawMacd(view1, view2) { 134 | const ctx = this.ctx; 135 | const theme = this.option.theme; 136 | 137 | let max = 0; 138 | let min = 0; 139 | this.state.macd.forEach((el, i) => { 140 | if (i < this.state.startIndex || i >= this.state.endIndex) { 141 | return; 142 | } 143 | let val = Math.max(el, this.state.dif[i], this.state.dea[i]); 144 | max = max > val ? max : val; 145 | val = Math.min(el, this.state.dif[i], this.state.dea[i]); 146 | min = min < val ? min : val; 147 | }); 148 | max = (max > Math.abs(min) ? max : Math.abs(min)) * 1.5; 149 | this.csiYAxisSector = [max, -max]; 150 | const yAxis = [max, max * 2 / 3, max / 3, -max / 3, -max * 2 / 3, -max]; 151 | 152 | ctx.textAlign = 'center'; 153 | ctx.textBaseline = 'middle'; 154 | ctx.fillStyle = this.colors.textColor; 155 | ctx.setLineDash([2 * this.dpr], 2 * this.dpr); 156 | ctx.strokeStyle = this.colors.splitLine; 157 | ctx.lineWidth = this.dpr * 0.5; 158 | for (let i = 1; i < yAxis.length - 1; i++) { 159 | ctx.fillText(this.setDP(yAxis[i]), view2.x + view2.w * 0.5, view2.y + i / (yAxis.length - 1) * view2.h); 160 | ctx.beginPath(); 161 | ctx.moveTo(0, view2.y + i / (yAxis.length - 1) * view2.h); 162 | ctx.lineTo(view2.x, view2.y + i / (yAxis.length - 1) * view2.h); 163 | ctx.stroke(); 164 | } 165 | 166 | ctx.setLineDash([]); 167 | ctx.lineWidth = this.dpr; 168 | ctx.fillStyle = this.colors.greenColor; 169 | ctx.strokeStyle = this.colors.greenColor; 170 | for (let i = this.state.startIndex, j = 0; i < this.state.endIndex; i++, j++) { 171 | if (i >= this.state.times.length) { 172 | break; 173 | } 174 | if (this.state.macd[i] > 0) { 175 | let y = view1.y + view1.h * 0.5; 176 | let w = view1.w / this.state.verticalRectNumber * 0.8; 177 | let x = j * view1.w / this.state.verticalRectNumber + view1.x + w * 0.1; 178 | let h = -this.state.macd[i] / max * view1.h * 0.5; 179 | if (Math.abs(this.state.macd[i]) > Math.abs(this.state.macd[i - 1])) { 180 | ctx.fillRect(x, y, w, h); 181 | } else { 182 | if (w <= this.dpr * 4) { 183 | ctx.fillRect(x, y, w, h); 184 | } else { 185 | ctx.strokeRect(x, y, w, h); 186 | } 187 | } 188 | } 189 | } 190 | ctx.fillStyle = this.colors.redColor; 191 | ctx.strokeStyle = this.colors.redColor; 192 | for (let i = this.state.startIndex, j = 0; i < this.state.endIndex; i++, j++) { 193 | if (i >= this.state.times.length) { 194 | break; 195 | } 196 | if (this.state.macd[i] <= 0) { 197 | let y = view1.y + view1.h * 0.5; 198 | let w = view1.w / this.state.verticalRectNumber * 0.8; 199 | let x = j * view1.w / this.state.verticalRectNumber + view1.x + w * 0.1; 200 | let h = -this.state.macd[i] / max * view1.h * 0.5; 201 | if (Math.abs(this.state.macd[i]) > Math.abs(this.state.macd[i - 1])) { 202 | ctx.fillRect(x, y, w, h); 203 | } else { 204 | if (w <= this.dpr * 4) { 205 | ctx.fillRect(x, y, w, h); 206 | } else { 207 | ctx.strokeRect(x, y, w, h); 208 | } 209 | } 210 | } 211 | } 212 | 213 | // dif 214 | ctx.strokeStyle = this.colors.ma7Color; 215 | ctx.beginPath(); 216 | for (let i = this.state.startIndex, j = 0; i < this.state.endIndex; i++, j++) { 217 | if (i >= this.state.times.length) { 218 | break; 219 | } 220 | let x = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 221 | let y = (max - this.state.dif[i]) / (2 * max) * view1.h + view1.y; 222 | if (j === 0) { 223 | ctx.moveTo(x, y); 224 | continue; 225 | } 226 | ctx.lineTo(x, y); 227 | } 228 | ctx.stroke(); 229 | 230 | // dea 231 | ctx.strokeStyle = this.colors.ma30Color; 232 | ctx.beginPath(); 233 | for (let i = this.state.startIndex, j = 0; i < this.state.endIndex; i++, j++) { 234 | if (i >= this.state.times.length) { 235 | break; 236 | } 237 | let x = j * view1.w / this.state.verticalRectNumber + 0.5 * view1.w / this.state.verticalRectNumber + view1.x; 238 | let y = (max - this.state.dea[i]) / (2 * max) * view1.h + view1.y; 239 | if (j === 0) { 240 | ctx.moveTo(x, y); 241 | continue; 242 | } 243 | ctx.lineTo(x, y); 244 | } 245 | ctx.stroke(); 246 | } 247 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { KLine, Depth } from './KLine.js'; 2 | const url = 'https://www.sosobtc.com/widgetembed/data/period?symbol=okcoinbtccny&step=' + 60; 3 | fetch('http://45.248.68.30:3000/data?url=' + window.encodeURIComponent(url)).then(res => { 4 | return res.json(); 5 | }).then(json => { 6 | var chart = new KLine(document.getElementById('app'), { 7 | width: 800, 8 | height: document.documentElement.clientHeight, 9 | intervalY: 30, 10 | theme: 'dark', 11 | priceFilter: data => Number(data.toFixed(5)), 12 | overYFilter: data => Number(data.toFixed(5)), 13 | }); 14 | chart.setData(json); 15 | setTimeout(function() { 16 | chart.setOption({ 17 | width: document.documentElement.clientWidth, 18 | height: document.documentElement.clientHeight, 19 | }); 20 | }, 10000); 21 | var socket = window.io('http://45.248.68.30:3000'); 22 | socket.emit('market.subscribe', 'btc:okcoin'); 23 | socket.on('update:trades', function(d) { 24 | for (let data of d) { 25 | let newTime = parseFloat(data.date); 26 | let newPrice = parseFloat(data.price); 27 | if (newTime - json[json.length - 1][0] < 60) { 28 | let hi = Math.max(json[json.length - 1][2], newPrice); 29 | let lo = Math.min(json[json.length - 1][3], newPrice); 30 | let close = data.price; 31 | json[json.length - 1][2] = hi; 32 | json[json.length - 1][3] = lo; 33 | json[json.length - 1][4] = newPrice; 34 | json[json.length - 1][5] += parseFloat(data.amount.toFixed(3)); 35 | chart.update(json); 36 | } else { 37 | json.push([json[json.length - 1][0] + 60, newPrice, newPrice, newPrice, newPrice, data.amount]); 38 | chart.update(json); 39 | } 40 | } 41 | }); 42 | 43 | var ele = document.getElementById('depth'); 44 | var depth = new Depth(ele, { width: 300, height: 400 }); 45 | socket.on('update:depth', function(data) { 46 | const buy = []; 47 | const sell = []; 48 | let bids = data.bids.replace(/\[|\]/g, '').split(','); 49 | let asks = data.asks.replace(/\[|\]/g, '').split(','); 50 | data.bids.replace(/\[|\]/g, '').split(',').forEach((el, i) => { 51 | let index = parseInt(i / 2); 52 | if (i % 2 === 0) { 53 | buy[index] = []; 54 | buy[index].push(Number(el)); 55 | } else { 56 | buy[index].push(Number(el)); 57 | } 58 | }); 59 | data.asks.replace(/\[|\]/g, '').split(',').map((el, i) => { 60 | let index = parseInt(i / 2); 61 | if (i % 2 === 0) { 62 | sell[index] = []; 63 | sell[index].push(Number(el)); 64 | } else { 65 | sell[index].push(Number(el)); 66 | } 67 | }); 68 | depth.setData({ buy, sell: sell.reverse() }); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/operation.js: -------------------------------------------------------------------------------- 1 | import { computAxis } from './draw'; 2 | export default function operation() { 3 | const overCtx = this.overCtx; 4 | const overCanvas = this.overCanvas; 5 | 6 | let isDown = false; 7 | let lastIndex = 0; 8 | let lastPos = { x: -1, y: -1 }; 9 | let lastTouchDistance = 0; 10 | 11 | let lock = false; 12 | 13 | const move = e => { 14 | const pos = this.getMousePos(e); 15 | const currentIndex = Math.floor((pos.x - this.views[0].x) / this.views[0].w * this.state.verticalRectNumber); 16 | var x = currentIndex * this.views[0].w / this.state.verticalRectNumber + 0.5 * this.views[0].w / this.state.verticalRectNumber + this.views[0].x; 17 | if (x != lastPos.x || pos.y != lastPos.y) { 18 | overCtx.clearRect(0, 0, this.width, this.height); 19 | if (isDown) { 20 | let num = lastIndex - currentIndex; 21 | if (this.state.startIndex + num < 0) { 22 | num = -this.state.startIndex; 23 | } 24 | if (this.state.endIndex + num > this.state.times.length + this.state.verticalRectNumber * 0.5) { 25 | num = this.state.times.length + this.state.verticalRectNumber * 0.5 - this.state.endIndex; 26 | } 27 | this.state.startIndex += num; 28 | this.state.endIndex += num; 29 | if (this.state.startIndex < 0) { 30 | this.state.startIndex = 0; 31 | this.state.endIndex = this.state.startIndex + this.state.verticalRectNumber; 32 | } 33 | this.draw(); 34 | } else { 35 | let flag = this.isInLineView(pos); 36 | if (flag !== false && currentIndex + this.state.startIndex < this.state.times.length) { 37 | this.overCanvas.style.cursor = 'none'; 38 | drawHairline.call(this, x, pos.y, currentIndex); 39 | } else { 40 | this.overCanvas.style.cursor = 'default'; 41 | } 42 | } 43 | lastIndex = currentIndex; 44 | lastPos = { x, y: pos.y }; 45 | } 46 | }; 47 | const scale = n => { 48 | if (n > 10) { 49 | n = 10; 50 | } 51 | if (n < -10) { 52 | n = -10; 53 | } 54 | const lastStartIndex = this.state.startIndex; 55 | const lastEndIndex = this.state.endIndex; 56 | const lastVerticalRectNumber = this.state.verticalRectNumber; 57 | this.state.startIndex -= n; 58 | this.state.endIndex += n; 59 | if (this.state.endIndex - this.state.startIndex > this.state.maxKLineNumber) { 60 | this.state.startIndex = lastStartIndex - (this.state.maxKLineNumber - lastVerticalRectNumber) / 2; 61 | this.state.endIndex = lastEndIndex + (this.state.maxKLineNumber - lastVerticalRectNumber) / 2; 62 | } 63 | if (this.state.endIndex - this.state.startIndex < this.state.minKLineNumber) { 64 | this.state.startIndex = lastStartIndex + (lastVerticalRectNumber - this.state.minKLineNumber) / 2; 65 | this.state.endIndex = lastEndIndex - (lastVerticalRectNumber - this.state.minKLineNumber) / 2; 66 | } 67 | this.state.verticalRectNumber = this.state.endIndex - this.state.startIndex; 68 | if (this.state.startIndex < 0) { 69 | this.state.endIndex -= this.state.startIndex; 70 | this.state.startIndex = 0; 71 | } 72 | if (this.state.startIndex >= this.state.times.length) { 73 | this.state.startIndex = this.state.times.length - 1; 74 | this.state.endIndex = this.state.startIndex + this.state.verticalRectNumber; 75 | } 76 | this.draw(); 77 | }; 78 | if (this.device == 'pc') { 79 | const mousedown = e => { 80 | isDown = true; 81 | const pos = this.getMousePos(e); 82 | const currentIndex = Math.floor((pos.x - this.views[0].x) / this.views[0].w * this.state.verticalRectNumber); 83 | lastIndex = currentIndex; 84 | }; 85 | const mouseup = () => { 86 | isDown = false; 87 | }; 88 | const mouseout = () => { 89 | isDown = false; 90 | overCtx.clearRect(0, 0, this.width, this.height); 91 | }; 92 | overCanvas.addEventListener('mousedown', mousedown); 93 | overCanvas.addEventListener('mouseup', mouseup); 94 | overCanvas.addEventListener('mouseout', mouseout); 95 | overCanvas.addEventListener('mousemove', move); 96 | overCanvas.addEventListener('wheel', function(e) { 97 | e.preventDefault(); 98 | let n = Number(e.deltaY.toFixed(0)); 99 | scale(n); 100 | }); 101 | } 102 | if (this.device == 'mb') { 103 | const touchstart = e => { 104 | isDown = true; 105 | if (e.targetTouches.length == 2) { 106 | const touch1 = this.getMousePos(e.targetTouches[0]); 107 | const touch2 = this.getMousePos(e.targetTouches[1]); 108 | lastTouchDistance = Math.sqrt(Math.pow(touch1.x - touch2.x, 2) + Math.pow(touch1.y - touch2.y, 2)); 109 | } 110 | const pos = this.getMousePos(e.targetTouches[0]); 111 | const currentIndex = Math.floor((pos.x - this.views[0].x) / this.views[0].w * this.state.verticalRectNumber); 112 | lastIndex = currentIndex; 113 | const x = currentIndex * this.views[0].w / this.state.verticalRectNumber + 0.5 * this.views[0].w / this.state.verticalRectNumber + this.views[0].x; 114 | let flag = this.isInLineView(pos); 115 | if (flag !== false && currentIndex + this.state.startIndex < this.state.times.length) { 116 | overCtx.clearRect(0, 0, this.width, this.height); 117 | drawHairline.call(this, x, pos.y, currentIndex); 118 | } 119 | }; 120 | const touchend = () => { 121 | isDown = false; 122 | }; 123 | const touchcancel = () => { 124 | isDown = false; 125 | overCtx.clearRect(0, 0, this.width, this.height); 126 | }; 127 | const touchmove = e => { 128 | e.preventDefault(); 129 | if (e.targetTouches.length === 2) { 130 | const touch1 = this.getMousePos(e.targetTouches[0]); 131 | const touch2 = this.getMousePos(e.targetTouches[1]); 132 | const currentDistance = Math.sqrt(Math.pow(touch1.x - touch2.x, 2) + Math.pow(touch1.y - touch2.y, 2)); 133 | let n = (this.state.verticalRectNumber - currentDistance / lastTouchDistance * this.state.verticalRectNumber); 134 | lastTouchDistance = currentDistance; 135 | if (n > 0) { 136 | n = Math.ceil(n); 137 | } else { 138 | n = Math.floor(n); 139 | } 140 | scale(n); 141 | } else { 142 | move(e.targetTouches[0]); 143 | } 144 | }; 145 | overCanvas.addEventListener('touchstart', touchstart); 146 | overCanvas.addEventListener('touchend', touchend); 147 | overCanvas.addEventListener('touchcancel', touchcancel); 148 | overCanvas.addEventListener('touchmove', touchmove); 149 | } 150 | } 151 | 152 | function drawHairline(x, y, currentIndex) { 153 | x = x || this.lastPos.x; 154 | y = y || this.lastPos.y; 155 | var overCtx = this.overCtx; 156 | overCtx.lineWidth = this.dpr; 157 | overCtx.strokeStyle = this.colors.subline; 158 | overCtx.beginPath(); 159 | // 画横线 160 | overCtx.moveTo(0, y); 161 | overCtx.lineTo(this.width, y); 162 | overCtx.stroke(); 163 | // 画竖线 164 | overCtx.moveTo(x, 0); 165 | overCtx.lineTo(x, this.height); 166 | overCtx.stroke(); 167 | overCtx.closePath(); 168 | 169 | // 画x轴的坐标 170 | var currentTime = this.state.times[this.state.startIndex + currentIndex]; 171 | overCtx.textAlign = 'center'; 172 | overCtx.textBaseline = 'bottom'; 173 | overCtx.fillStyle = this.colors.background; 174 | overCtx.fillRect(x - overCtx.measureText(currentTime).width * 0.5 - 10 * this.dpr, this.height - 50, overCtx.measureText(currentTime).width + 20 * this.dpr, 50 - this.dpr); 175 | overCtx.strokeStyle = this.colors.textFrameColor; 176 | overCtx.strokeRect(x - overCtx.measureText(currentTime).width * 0.5 - 10 * this.dpr, this.height - 50, overCtx.measureText(currentTime).width + 20 * this.dpr, 50 - this.dpr); 177 | overCtx.fillStyle = this.colors.textColor; 178 | overCtx.fillText(this.option.overTimeFilter(currentTime), x, this.height - 7); 179 | 180 | // 画y轴坐标 181 | // 根据intervalY计算y轴显示的最大和最小的数值 182 | const { max, min } = computAxis.call(this); 183 | let view = this.views[1]; 184 | let w = this.width - view.x; 185 | overCtx.textAlign = 'right'; 186 | overCtx.textBaseline = 'middle'; 187 | overCtx.fillStyle = this.colors.background; 188 | overCtx.fillRect(view.x, y - 16, w, 32); 189 | overCtx.strokeStyle = this.colors.textFrameColor; 190 | overCtx.strokeRect(view.x, y - 16, w, 32); 191 | overCtx.fillStyle = this.colors.textColor; 192 | 193 | const csiStr = this.option.csi2[0]; 194 | let flag = this.isInLineView({ x, y }); 195 | if (flag === 0) { 196 | const yText = max - (max - min) * (y - view.y) / view.h; 197 | overCtx.fillText(this.option.overYFilter(yText), view.x + view.w, y); 198 | } else if (flag === 1) { 199 | view = this.views[3]; 200 | if (csiStr === 'volume') { 201 | const yText = (1 - (y - view.y) / view.h) * (this.csiYAxisSector[0] - this.csiYAxisSector[1]); 202 | overCtx.fillText(this.setDP(yText), view.x + view.w, y); 203 | } 204 | if (csiStr === 'macd') { 205 | const yText = this.csiYAxisSector[1] * (y - view.y) / view.h + this.csiYAxisSector[0] * (1 - (y - view.y) / view.h); 206 | overCtx.fillText(this.setDP(yText), view.x + view.w, y); 207 | } 208 | } 209 | this.select.call(this, { 210 | time: this.state.times[currentIndex + this.state.startIndex], 211 | start: this.state.start[currentIndex + this.state.startIndex], 212 | hi: this.state.hi[currentIndex + this.state.startIndex], 213 | lo: this.state.lo[currentIndex + this.state.startIndex], 214 | close: this.state.close[currentIndex + this.state.startIndex], 215 | volume: this.state.volume[currentIndex + this.state.startIndex], 216 | [this.option.csi + 7]: this.state[this.option.csi + 7][currentIndex + this.state.startIndex], 217 | [this.option.csi + 30]: this.state[this.option.csi + 30][currentIndex + this.state.startIndex], 218 | }, 0); 219 | 220 | let ma7Color = this.colors.ma7Color; 221 | let ma30Color = this.colors.ma30Color; 222 | if (csiStr === 'volume') { 223 | this.select.call(this, { 224 | volume: this.state.volume[currentIndex + this.state.startIndex], 225 | ma7: this.state.volumeMa7[currentIndex + this.state.startIndex], 226 | ma30: this.state.volumeMa30[currentIndex + this.state.startIndex], 227 | }, 1); 228 | } 229 | if (csiStr === 'macd') { 230 | this.select.call(this, { 231 | dif: this.state.dif[currentIndex + this.state.startIndex], 232 | dea: this.state.dea[currentIndex + this.state.startIndex], 233 | macd: this.state.macd[currentIndex + this.state.startIndex], 234 | }, 1); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/select.js: -------------------------------------------------------------------------------- 1 | function transformKey(key) { 2 | if (key === 'time') { 3 | return '时间'; 4 | } else if (key === 'start') { 5 | return '开'; 6 | } else if (key === 'hi') { 7 | return '高'; 8 | } else if (key === 'lo') { 9 | return '低'; 10 | } else if (key === 'close') { 11 | return '收'; 12 | } else if (key === 'volume') { 13 | return '量'; 14 | } else if (key === 'macd') { 15 | return 'MACD'; 16 | } else if (key === 'ema7') { 17 | return 'EMA7'; 18 | } else if (key === 'ema30') { 19 | return 'EMA30'; 20 | } else if (key === 'ma7') { 21 | return 'MA7'; 22 | } else if (key === 'ma30') { 23 | return 'MA30'; 24 | } else if (key === 'dif') { 25 | return 'DIF'; 26 | } else if (key === 'dea') { 27 | return 'DEA'; 28 | } else { 29 | return key; 30 | } 31 | } 32 | 33 | function setStyle(key, ctx) { 34 | key = key.toLowerCase(); 35 | if (key === 'ema7' || key === 'ma7' || key === 'dif') { 36 | ctx.fillStyle = this.colors.ma7Color; 37 | } else if (key === 'ema30' || key === 'ma30' || key === 'dea') { 38 | ctx.fillStyle = this.colors.ma30Color; 39 | } else if (key === 'macd') { 40 | ctx.fillStyle = this.colors.macdColor; 41 | } else { 42 | ctx.fillStyle = this.colors.textColorLight; 43 | } 44 | } 45 | 46 | export default function(data, flag) { 47 | let overCtx = this.overCtx; 48 | overCtx.textAlign = 'left'; 49 | overCtx.textBaseline = 'top'; 50 | if (flag === 0) { 51 | if (this.device === 'pc') { 52 | let x = 5; 53 | let y = 5; 54 | for (let i = 0; i < Object.keys(data).length; i++) { 55 | let key = Object.keys(data)[i]; 56 | let text; 57 | if (key === 'time') { 58 | text = '时间:' + this.option.overTimeFilter(data[key]); 59 | } else { 60 | text = transformKey(key) + ':' + data[key]; 61 | } 62 | if (overCtx.measureText(text).width + x + 40 > this.views[0].x + this.views[0].w) { 63 | x = 5; 64 | y += 40; 65 | } 66 | setStyle.call(this, key, overCtx); 67 | overCtx.fillText(text, x, y); 68 | x += overCtx.measureText(text).width + 40; 69 | } 70 | } else { 71 | let text = `${this.option.overTimeFilter(data.time)} 开${data.start} 高${data.hi} 低${data.lo} 收${data.close}`; 72 | overCtx.textAlign = 'center'; 73 | overCtx.textBaseline = 'middle'; 74 | overCtx.fillStyle = '#343f4d'; 75 | overCtx.fillRect(0, 0, this.width, 32 * this.dpr); 76 | overCtx.fillStyle = this.colors.textColor; 77 | overCtx.fillText(text, this.width * 0.5, 16 * this.dpr); 78 | let x = 5; 79 | let y = 32 * this.dpr + 5; 80 | overCtx.textAlign = 'left'; 81 | overCtx.textBaseline = 'top'; 82 | for (let i = 0; i < Object.keys(data).length; i++) { 83 | let key = Object.keys(data)[i]; 84 | if (/(time|start|hi|lo|end)/g.test(key)) { 85 | continue; 86 | } 87 | text = transformKey(key) + ':' + data[key]; 88 | if (overCtx.measureText(text).width + x + 40 > this.views[0].x + this.views[0].w) { 89 | x = 5; 90 | y += 40; 91 | } 92 | setStyle.call(this, key, overCtx); 93 | overCtx.fillText(text, x, y); 94 | x += overCtx.measureText(text).width + 40; 95 | } 96 | } 97 | } else if (flag === 1) { 98 | let x = 5; 99 | let y = this.views[2].y; 100 | for (let i = 0; i < Object.keys(data).length; i++) { 101 | let key = Object.keys(data)[i]; 102 | let text = transformKey(key) + ':' + data[key]; 103 | if (overCtx.measureText(text).width + x + 40 > this.views[0].x + this.views[0].w) { 104 | x = 5; 105 | y += 40; 106 | } 107 | setStyle.call(this, key, overCtx); 108 | overCtx.fillText(text, x, y); 109 | x += overCtx.measureText(text).width + 40; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/setData.js: -------------------------------------------------------------------------------- 1 | export default function setData(data) { 2 | this.data = data; 3 | let times = []; 4 | let timeStr = []; 5 | let start = []; 6 | let hi = []; 7 | let lo = []; 8 | let close = []; 9 | let volume = []; 10 | data.forEach(d => { 11 | times.push(d[0]); 12 | timeStr.push(this.option.timeFilter(d[0])); 13 | start.push(d[1]); 14 | hi.push(d[2]); 15 | lo.push(d[3]); 16 | close.push(d[4]); 17 | volume.push(d[5]); 18 | }); 19 | this.state = { 20 | startIndex: data.length > 30 ? data.length - 30 : 0, 21 | endIndex: data.length > 30 ? data.length : 30, 22 | verticalRectNumber: 30, 23 | isDown: false, 24 | times, 25 | timeStr, 26 | start, 27 | hi, 28 | lo, 29 | close, 30 | volume, 31 | ma30: close.map((el, i) => { 32 | if (i < 29) { 33 | return el; 34 | } else { 35 | let sum = 0; 36 | for (let index = i; index > i - 30; index--) { 37 | sum += close[index]; 38 | } 39 | return this.setDP(sum / 30); 40 | } 41 | }), 42 | ma7: close.map((el, i) => { 43 | if (i < 6) { 44 | return el; 45 | } else { 46 | let sum = 0; 47 | for (let index = i; index > i - 7; index--) { 48 | sum += close[index]; 49 | } 50 | return this.setDP(sum / 7); 51 | } 52 | }), 53 | volumeMa7: volume.map((el, i) => { 54 | if (i < 6) { 55 | return el; 56 | } else { 57 | let sum = 0; 58 | for (let index = i; index > i - 7; index--) { 59 | sum += volume[index]; 60 | } 61 | return this.setDP(sum / 7); 62 | } 63 | }), 64 | volumeMa30: volume.map((el, i) => { 65 | if (i < 29) { 66 | return el; 67 | } else { 68 | let sum = 0; 69 | for (let index = i; index > i - 30; index--) { 70 | sum += volume[index]; 71 | } 72 | return this.setDP(sum / 30); 73 | } 74 | }), 75 | }; 76 | this.state.ema30 = []; 77 | this.state.close.forEach((el, i) => { 78 | if (i === 0) { 79 | this.state.ema30[i] = el; 80 | } else { 81 | let val = 2 / 31 * (this.state.close[i] - this.state.ema30[i - 1]) + this.state.ema30[i - 1]; 82 | this.state.ema30[i] = this.setDP(val); 83 | } 84 | }); 85 | this.state.ema7 = []; 86 | this.state.close.forEach((el, i) => { 87 | if (i === 0) { 88 | this.state.ema7[i] = el; 89 | } else { 90 | let val = 2 / 8 * (this.state.close[i] - this.state.ema7[i - 1]) + this.state.ema7[i - 1]; 91 | this.state.ema7[i] = this.setDP(val); 92 | } 93 | }); 94 | this.state.ema15 = []; 95 | this.state.close.forEach((el, i) => { 96 | if (i === 0) { 97 | this.state.ema15[i] = el; 98 | } else { 99 | let val = 2 / 16 * (this.state.close[i] - this.state.ema15[i - 1]) + this.state.ema15[i - 1]; 100 | this.state.ema15[i] = this.setDP(val); 101 | } 102 | }); 103 | this.state.ema26 = []; 104 | this.state.close.forEach((el, i) => { 105 | if (i === 0) { 106 | this.state.ema26[i] = el; 107 | } else { 108 | let val = 2 / 27 * (this.state.close[i] - this.state.ema26[i - 1]) + this.state.ema26[i - 1]; 109 | this.state.ema26[i] = this.setDP(val); 110 | } 111 | }); 112 | this.state.ema12 = []; 113 | this.state.close.forEach((el, i) => { 114 | if (i === 0) { 115 | this.state.ema12[i] = el; 116 | } else { 117 | let val = 2 / 13 * (this.state.close[i] - this.state.ema12[i - 1]) + this.state.ema12[i - 1]; 118 | this.state.ema12[i] = this.setDP(val); 119 | } 120 | }); 121 | this.state.dif = this.state.ema12.map((el, i) => { 122 | let val = el - this.state.ema26[i]; 123 | return this.setDP(val); 124 | }); 125 | this.state.dea = []; 126 | this.state.dif.forEach((el, i) => { 127 | if (i === 0) { 128 | this.state.dea[i] = el; 129 | } else { 130 | let val = this.state.dea[i - 1] * 0.8 + el * 0.2; 131 | this.state.dea[i] = this.setDP(val); 132 | } 133 | }); 134 | this.state.macd = this.state.dif.map((el, i) => { 135 | let val = (el - this.state.dea[i]) * 2; 136 | return this.setDP(val); 137 | }); 138 | this.state.maxKLineNumber = parseInt(this.width / 2 / this.dpr) % 2 === 0 ? parseInt(this.width / 2 / this.dpr) : parseInt(this.width / 2 / this.dpr) - 1; 139 | this.state.minKLineNumber = 16; 140 | if (this.state.maxKLineNumber > times.length * 2) { 141 | this.state.maxKLineNumber = times.length * 2; 142 | } 143 | this.draw(true); 144 | } 145 | -------------------------------------------------------------------------------- /src/update.js: -------------------------------------------------------------------------------- 1 | export default function update(data) { 2 | this.data = data; 3 | let lastState = this.state; 4 | let times = []; 5 | let timeStr = []; 6 | let start = []; 7 | let hi = []; 8 | let lo = []; 9 | let close = []; 10 | let volume = []; 11 | data.forEach(d => { 12 | times.push(d[0]); 13 | timeStr.push(this.option.timeFilter(d[0])); 14 | start.push(d[1]); 15 | hi.push(d[2]); 16 | lo.push(d[3]); 17 | close.push(d[4]); 18 | volume.push(d[5]); 19 | }); 20 | this.state = { 21 | startIndex: lastState.endIndex !== lastState.times.length ? lastState.startIndex : lastState.startIndex + times.length - lastState.times.length, 22 | endIndex: lastState.endIndex !== lastState.times.length ? lastState.endIndex : lastState.endIndex + times.length - lastState.times.length, 23 | verticalRectNumber: lastState.verticalRectNumber, 24 | isDown: false, 25 | times, 26 | timeStr, 27 | start, 28 | hi, 29 | lo, 30 | close, 31 | volume, 32 | ma30: close.map((el, i) => { 33 | if (i < 29) { 34 | return el; 35 | } else { 36 | let sum = 0; 37 | for (let index = i; index > i - 30; index--) { 38 | sum += close[index]; 39 | } 40 | return this.setDP(sum / 30); 41 | } 42 | }), 43 | ma7: close.map((el, i) => { 44 | if (i < 6) { 45 | return el; 46 | } else { 47 | let sum = 0; 48 | for (let index = i; index > i - 7; index--) { 49 | sum += close[index]; 50 | } 51 | return this.setDP(sum / 7); 52 | } 53 | }), 54 | volumeMa7: volume.map((el, i) => { 55 | if (i < 6) { 56 | return el; 57 | } else { 58 | let sum = 0; 59 | for (let index = i; index > i - 7; index--) { 60 | sum += volume[index]; 61 | } 62 | return this.setDP(sum / 7); 63 | } 64 | }), 65 | volumeMa30: volume.map((el, i) => { 66 | if (i < 29) { 67 | return el; 68 | } else { 69 | let sum = 0; 70 | for (let index = i; index > i - 30; index--) { 71 | sum += volume[index]; 72 | } 73 | return this.setDP(sum / 30); 74 | } 75 | }), 76 | }; 77 | this.state.ema30 = []; 78 | this.state.close.forEach((el, i) => { 79 | if (i === 0) { 80 | this.state.ema30[i] = el; 81 | } else { 82 | let val = 2 / 31 * (this.state.close[i] - this.state.ema30[i - 1]) + this.state.ema30[i - 1]; 83 | this.state.ema30[i] = this.setDP(val); 84 | } 85 | }); 86 | this.state.ema7 = []; 87 | this.state.close.forEach((el, i) => { 88 | if (i === 0) { 89 | this.state.ema7[i] = el; 90 | } else { 91 | let val = 2 / 8 * (this.state.close[i] - this.state.ema7[i - 1]) + this.state.ema7[i - 1]; 92 | this.state.ema7[i] = this.setDP(val); 93 | } 94 | }); 95 | this.state.ema15 = []; 96 | this.state.close.forEach((el, i) => { 97 | if (i === 0) { 98 | this.state.ema15[i] = el; 99 | } else { 100 | let val = 2 / 16 * (this.state.close[i] - this.state.ema15[i - 1]) + this.state.ema15[i - 1]; 101 | this.state.ema15[i] = this.setDP(val); 102 | } 103 | }); 104 | this.state.ema26 = []; 105 | this.state.close.forEach((el, i) => { 106 | if (i === 0) { 107 | this.state.ema26[i] = el; 108 | } else { 109 | let val = 2 / 27 * (this.state.close[i] - this.state.ema26[i - 1]) + this.state.ema26[i - 1]; 110 | this.state.ema26[i] = this.setDP(val); 111 | } 112 | }); 113 | this.state.ema12 = []; 114 | this.state.close.forEach((el, i) => { 115 | if (i === 0) { 116 | this.state.ema12[i] = el; 117 | } else { 118 | let val = 2 / 13 * (this.state.close[i] - this.state.ema12[i - 1]) + this.state.ema12[i - 1]; 119 | this.state.ema12[i] = this.setDP(val); 120 | } 121 | }); 122 | this.state.dif = this.state.ema12.map((el, i) => { 123 | let val = el - this.state.ema26[i]; 124 | return this.setDP(val); 125 | }); 126 | this.state.dea = []; 127 | this.state.dif.forEach((el, i) => { 128 | if (i === 0) { 129 | this.state.dea[i] = el; 130 | } else { 131 | let val = this.state.dea[i - 1] * 0.8 + el * 0.2; 132 | this.state.dea[i] = this.setDP(val); 133 | } 134 | }); 135 | this.state.macd = this.state.dif.map((el, i) => { 136 | let val = (el - this.state.dea[i]) * 2; 137 | return this.setDP(val); 138 | }); 139 | this.state.maxKLineNumber = parseInt(this.width / 2 / this.dpr) % 2 === 0 ? parseInt(this.width / 2 / this.dpr) : parseInt(this.width / 2 / this.dpr) - 1; 140 | this.state.minKLineNumber = 16; 141 | if (this.state.maxKLineNumber > times.length * 2) { 142 | this.state.maxKLineNumber = times.length * 2; 143 | } 144 | this.draw(true); 145 | } 146 | -------------------------------------------------------------------------------- /src2/Depth.js: -------------------------------------------------------------------------------- 1 | function roundedRect(ctx, x, y, width, height, radius) { 2 | ctx.beginPath(); 3 | ctx.moveTo(x, y + radius); 4 | ctx.lineTo(x, y + height - radius); 5 | ctx.quadraticCurveTo(x, y + height, x + radius, y + height); 6 | ctx.lineTo(x + width - radius, y + height); 7 | ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius); 8 | ctx.lineTo(x + width, y + radius); 9 | ctx.quadraticCurveTo(x + width, y, x + width - radius, y); 10 | ctx.lineTo(x + radius, y); 11 | ctx.quadraticCurveTo(x, y, x, y + radius); 12 | ctx.fill(); 13 | } 14 | 15 | export default function Depth(ele, option) { 16 | this.device = (navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) ? 'mb' : 'pc'; 17 | this.option = option; 18 | this.dpr = window.devicePixelRatio; 19 | ele.style.width = option.width + 'px'; 20 | ele.style.height = option.height + 'px'; 21 | 22 | let canvas = document.createElement('canvas'); 23 | canvas.style.width = option.width + 'px'; 24 | canvas.style.height = option.height + 'px'; 25 | canvas.width = option.width * this.dpr; 26 | canvas.height = option.height * this.dpr; 27 | this.canvas = canvas; 28 | 29 | this.width = option.width * this.dpr; 30 | this.height = option.height * this.dpr; 31 | this.ctx = canvas.getContext('2d'); 32 | this.ctx.font = this.dpr * (option.fontSize || 14) + 'px sans-serif'; 33 | 34 | ele.appendChild(canvas); 35 | 36 | this.theme = option.theme || 'white'; 37 | this.colors = { 38 | background: this.theme === 'dark' ? 'black' : 'white', 39 | fontColor: this.theme === 'dark' ? '#656565' : '#656565', 40 | splitColor: this.theme === 'dark' ? '#333' : '#ccc', 41 | }; 42 | 43 | if (this.device === 'pc') { 44 | canvas.addEventListener('mousemove', e => { 45 | this.pos = this.getMousePos(e); 46 | this.setData(); 47 | }); 48 | canvas.addEventListener('mouseout', e => { 49 | this.pos = null; 50 | this.setData(); 51 | }); 52 | canvas.addEventListener('mousecancel', e => { 53 | this.pos = null; 54 | this.setData(); 55 | }); 56 | } else { 57 | canvas.addEventListener('touchstart', e => { 58 | this.pos = this.getMousePos(e.targetTouches[0]); 59 | this.setData(); 60 | }); 61 | } 62 | } 63 | 64 | Depth.prototype.setData = function(data) { 65 | if (!data) { 66 | data = this.data; 67 | } else { 68 | this.data = data; 69 | } 70 | const { buy, sell } = this.data; 71 | 72 | const buyPrice = []; 73 | const sellPrice = []; 74 | const buyVolume = []; 75 | const sellVolume = []; 76 | buy.forEach(el => { 77 | buyPrice.push(parseFloat(el[0])); 78 | buyVolume.push(parseFloat(el[1])); 79 | }); 80 | sell.forEach(el => { 81 | sellPrice.push(parseFloat(el[0])); 82 | sellVolume.push(parseFloat(el[1])); 83 | }); 84 | let buyDepth = []; 85 | for (let i = 0; i < buyVolume.length; i++) { 86 | if (i === 0) { 87 | buyDepth[i] = parseFloat(buyVolume[i]); 88 | continue; 89 | } 90 | buyDepth[i] = buyDepth[i - 1] + parseFloat(buyVolume[i]); 91 | } 92 | let sellDepth = []; 93 | for (let i = 0; i < sellVolume.length; i++) { 94 | if (i === 0) { 95 | sellDepth[i] = parseFloat(sellVolume[i]); 96 | continue; 97 | } 98 | sellDepth[i] = sellDepth[i - 1] + parseFloat(sellVolume[i]); 99 | } 100 | 101 | const maxVolume = Math.max(buyDepth[buyDepth.length - 1], sellDepth[sellDepth.length - 1]) * 1.2; 102 | let n = (maxVolume * 0.2).toFixed(0).length; 103 | const interval = Math.ceil(maxVolume * 0.2 / Math.pow(10, n - 1)) * Math.pow(10, n - 1); 104 | const yAxis = []; 105 | for (let i = interval; i < maxVolume; i += interval) { 106 | yAxis.unshift(i); 107 | } 108 | 109 | let ctx = this.ctx; 110 | ctx.lineWidth = this.dpr; 111 | ctx.clearRect(0, 0, this.width, this.height); 112 | ctx.fillStyle = this.colors.background; 113 | ctx.fillRect(0, 0, this.width, this.height); 114 | 115 | let maxLength = 0; 116 | for (let i = interval; i < maxVolume; i += interval) { 117 | maxLength = Math.max(maxLength, ctx.measureText((i >= 10000 ? (i / 1000) + 'k' : i).toString()).width); 118 | } 119 | this.contentWidth = this.width - maxLength - 10; 120 | this.contentHeight = this.height - this.dpr * 50; 121 | 122 | this.ctx.font = this.dpr * (this.option.fontSize || 14) + 'px sans-serif'; 123 | ctx.textAlign = 'center'; 124 | ctx.textBaseline = 'middle'; 125 | ctx.fillStyle = '#2b8043'; 126 | roundedRect(ctx, 40 * this.dpr, 20 * this.dpr, 160 * this.dpr, 80 * this.dpr, 24 * this.dpr); 127 | ctx.fillStyle = 'white'; 128 | ctx.fillText('买单', 120 * this.dpr, 60 * this.dpr); 129 | 130 | ctx.fillStyle = '#db2f1a'; 131 | roundedRect(ctx, this.contentWidth - 40 * this.dpr - 160 * this.dpr, 20 * this.dpr, 160 * this.dpr, 80 * this.dpr, 24 * this.dpr); 132 | ctx.fillStyle = 'white'; 133 | ctx.fillText('卖单', this.contentWidth - 120 * this.dpr, 60 * this.dpr); 134 | this.ctx.font = this.dpr * (this.option.fontSize || 14) + 'px sans-serif'; 135 | 136 | n = 0; 137 | if ((sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]) >= 1) { 138 | n = (sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]).toFixed(0).length; 139 | } else { 140 | if ((sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]) < 0.000001) { 141 | let str = ((sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]) * 100000).toString().split('.')[1]; 142 | for (let i = 0; i < str.length; i++) { 143 | if (str.charAt(i) == 0) { 144 | n--; 145 | } 146 | } 147 | n -= 5; 148 | } else { 149 | let str = (sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]).toString().split('.')[1]; 150 | for (let i = 0; i < str.length; i++) { 151 | if (str.charAt(i) == 0) { 152 | n--; 153 | } 154 | } 155 | } 156 | } 157 | let number = (this.option.priceDecimal || 2) > 5 ? 0.5 : 0.25; 158 | const intervalX = Math.ceil((sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]) * number / Math.pow(10, n - 2)) * Math.pow(10, n - 2); 159 | ctx.fillStyle = this.colors.fontColor; 160 | ctx.textAlign = 'center'; 161 | ctx.textBaseline = 'top'; 162 | for (let i = buyPrice[buyPrice.length - 1] + intervalX; i < sellPrice[sellPrice.length - 1]; i += intervalX) { 163 | ctx.fillText(i.toFixed(this.option.priceDecimal || 2), (i - buyPrice[buyPrice.length - 1]) / (sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]) * this.contentWidth, this.contentHeight); 164 | } 165 | 166 | ctx.textAlign = 'left'; 167 | ctx.textBaseline = 'middle'; 168 | ctx.fillStyle = this.colors.fontColor; 169 | ctx.save(); 170 | ctx.strokeStyle = this.colors.splitColor; 171 | ctx.setLineDash([2, 2]); 172 | for (let i = interval; i < maxVolume; i += interval) { 173 | let y = this.contentHeight - this.contentHeight * i / maxVolume; 174 | ctx.fillText(i >= 10000 ? (i / 1000) + 'k' : i, this.contentWidth + 5, y); 175 | ctx.beginPath(); 176 | ctx.moveTo(0, y); 177 | ctx.lineTo(this.contentWidth, y); 178 | ctx.stroke(); 179 | } 180 | ctx.restore(); 181 | 182 | // 买单 183 | const p1 = (buyPrice[0] - buyPrice[buyPrice.length - 1]) / (sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]); 184 | ctx.lineWidth = this.dpr * 3; 185 | ctx.beginPath(); 186 | for (let i = 0; i < buyDepth.length; i++) { 187 | const p = (buyPrice[i] - buyPrice[buyPrice.length - 1]) / (buyPrice[0] - buyPrice[buyPrice.length - 1]); 188 | if (i === buyDepth.length - 1) { 189 | ctx.lineTo(this.contentWidth * p1 * p, this.contentHeight - buyDepth[i] / maxVolume * this.contentHeight); 190 | } else if (i === 0) { 191 | ctx.moveTo(this.contentWidth * p1 * p, this.contentHeight); 192 | ctx.lineTo(this.contentWidth * p1 * p, this.contentHeight - buyDepth[i] / maxVolume * this.contentHeight); 193 | } else { 194 | ctx.lineTo(this.contentWidth * p1 * p, this.contentHeight - buyDepth[i] / maxVolume * this.contentHeight); 195 | } 196 | } 197 | ctx.lineTo(0, this.contentHeight); 198 | ctx.closePath(); 199 | let lineargradient = ctx.createLinearGradient(0, 0, 0, this.contentHeight); 200 | lineargradient.addColorStop(0, 'rgba(54, 168, 83, 1)'); 201 | lineargradient.addColorStop(1, 'rgba(171, 205, 82, 0.2)'); 202 | ctx.fillStyle = lineargradient; 203 | ctx.fill(); 204 | ctx.strokeStyle = '#246b38'; 205 | ctx.stroke(); 206 | 207 | // 卖单 208 | const p2 = (sellPrice[sellPrice.length - 1] - sellPrice[0]) / (sellPrice[sellPrice.length - 1] - buyPrice[buyPrice.length - 1]); 209 | ctx.beginPath(); 210 | for (let i = 0; i < sellPrice.length; i++) { 211 | const p = (sellPrice[i] - sellPrice[0]) / (sellPrice[sellPrice.length - 1] - sellPrice[0]); 212 | if (i === sellPrice.length - 1) { 213 | ctx.lineTo(this.contentWidth * (1 - p2) + this.contentWidth * p2 * p, this.contentHeight - sellDepth[i] / maxVolume * this.contentHeight); 214 | } else if (i === 0) { 215 | ctx.moveTo(this.contentWidth * (1 - p2) + this.contentWidth * p2 * p, this.contentHeight); 216 | ctx.lineTo(this.contentWidth * (1 - p2) + this.contentWidth * p2 * p, this.contentHeight - sellDepth[i] / maxVolume * this.contentHeight); 217 | } else { 218 | ctx.lineTo(this.contentWidth * (1 - p2) + this.contentWidth * p2 * p, this.contentHeight - sellDepth[i] / maxVolume * this.contentHeight); 219 | } 220 | } 221 | ctx.lineTo(this.contentWidth, this.contentHeight); 222 | ctx.closePath(); 223 | lineargradient = ctx.createLinearGradient(this.contentWidth, 0, this.contentWidth, this.contentHeight); 224 | lineargradient.addColorStop(0, 'rgba(216, 34, 13, 1)'); 225 | lineargradient.addColorStop(1, 'rgba(233, 84, 21, 0.2)'); 226 | ctx.fillStyle = lineargradient; 227 | ctx.fill(); 228 | ctx.strokeStyle = '#b81c0b'; 229 | ctx.stroke(); 230 | 231 | ctx.strokeStyle = this.colors.fontColor; 232 | ctx.strokeRect(0, 0, this.contentWidth, this.contentHeight); 233 | 234 | if (this.pos && this.pos.x <= this.contentWidth && this.pos.y <= this.contentHeight) { 235 | let x; 236 | let y; 237 | let rectH = (this.option.fontSize || 14) * 3 * this.dpr; 238 | let text; 239 | let title = ''; 240 | if (this.pos.x >= (this.contentWidth * (1 - p2))) { 241 | const currentPrice = (this.pos.x - (1 - p2) * this.contentWidth) / (this.contentWidth * p2) * (sellPrice[sellPrice.length - 1] - sellPrice[0]) + sellPrice[0]; 242 | let i; 243 | for (let index = 0; index < sellPrice.length; index++) { 244 | if (index === sellPrice.length - 1) { 245 | i = index; 246 | break; 247 | } 248 | if (currentPrice >= sellPrice[index] && currentPrice < sellPrice[index + 1]) { 249 | i = index; 250 | break; 251 | } 252 | } 253 | text = '价钱:' + sell[i][0]; 254 | title = '卖单:' + Number(sellDepth[i].toFixed(4)); 255 | ctx.beginPath(); 256 | const p = (sellPrice[i] - sellPrice[0]) / (sellPrice[sellPrice.length - 1] - sellPrice[0]); 257 | x = this.contentWidth * (1 - p2) + this.contentWidth * p2 * p; 258 | y = this.contentHeight - sellDepth[i] / maxVolume * this.contentHeight; 259 | ctx.arc(x, y, 10 * this.dpr, 0, Math.PI * 2, true); 260 | ctx.closePath(); 261 | ctx.fillStyle = 'rgb(255, 0, 0)'; 262 | ctx.fill(); 263 | ctx.strokeStyle = 'white'; 264 | ctx.stroke(); 265 | } else if (this.pos.x <= (this.contentWidth * p1)) { 266 | let currentPrice = this.pos.x / (this.contentWidth * p1) * (buyPrice[0] - buyPrice[buyPrice.length - 1]) + buyPrice[buyPrice.length - 1]; 267 | let i; 268 | for (let index = 0; index < buyPrice.length; index++) { 269 | if (index === buyPrice.length - 1) { 270 | i = index; 271 | break; 272 | } 273 | if (currentPrice <= buyPrice[index] && currentPrice > buyPrice[index + 1]) { 274 | i = index; 275 | break; 276 | } 277 | } 278 | text = '价钱:' + buy[buy.length - 1 - i][0]; 279 | title = '买单:' + Number(buyDepth[buyDepth.length - 1 - i].toFixed(4)); 280 | ctx.beginPath(); 281 | const p = (buyPrice[i] - buyPrice[buyPrice.length - 1]) / (buyPrice[0] - buyPrice[buyPrice.length - 1]); 282 | x = this.contentWidth * p1 * p; 283 | y = this.contentHeight - buyDepth[i] / maxVolume * this.contentHeight; 284 | ctx.arc(x, y, 10 * this.dpr, 0, Math.PI * 2, true); 285 | ctx.closePath(); 286 | ctx.fillStyle = 'rgb(0, 255, 0)'; 287 | ctx.fill(); 288 | ctx.strokeStyle = 'white'; 289 | ctx.stroke(); 290 | } else { 291 | return; 292 | } 293 | ctx.strokeStyle = 'white'; 294 | 295 | let rectW = Math.max(ctx.measureText(title).width, ctx.measureText(text).width) + 30; 296 | x = x > this.contentWidth * 0.5 ? x - 10 : x + 10; 297 | y = y > this.contentHeight * 0.5 ? y - 10 : y + 10; 298 | rectW = x > this.contentWidth * 0.5 ? -rectW : rectW; 299 | rectH = y > this.contentHeight * 0.5 ? -rectH : rectH; 300 | ctx.save(); 301 | ctx.shadowColor = this.colors.fontColor; 302 | ctx.shadowOffsetX = 0; 303 | ctx.shadowOffsetY = 0; 304 | ctx.shadowBlur = 3; 305 | ctx.strokeRect(x, y, rectW, rectH); 306 | ctx.restore(); 307 | 308 | ctx.fillStyle = '#656565'; 309 | ctx.textAlign = 'left'; 310 | ctx.textBaseline = 'middle'; 311 | let textX = x > this.contentWidth * 0.5 ? x + rectW + 10 : x + 10; 312 | let textY = y > this.contentHeight * 0.5 ? y + rectH * 2 / 3 : y + rectH / 3; 313 | ctx.fillText(text, textX, textY); 314 | textY = y > this.contentHeight * 0.5 ? y + rectH / 3 : y + rectH * 2 / 3; 315 | ctx.fillText(title, textX, textY); 316 | } 317 | }; 318 | -------------------------------------------------------------------------------- /src2/Depth2.js: -------------------------------------------------------------------------------- 1 | export default function Depth(canvas, data, option = {}) { 2 | this.dpr = canvas.width / canvas.getBoundingClientRect().width; 3 | this.canvas = canvas; 4 | this.setOption(option); 5 | this.data = data; 6 | 7 | this.colors = { 8 | background: this.option.theme === 'dark' ? '#0e2029' : '#ebf5fa', 9 | fontColor: this.option.theme === 'dark' ? '#878f94' : '#666', 10 | splitColor: this.option.theme === 'dark' ? '#878f94' : '#666', 11 | greenColor: this.option.theme === 'dark' ? 'rgb(138,224,63)' : 'rgb(138,224,63)', 12 | greenBackgroundColor: this.option.theme === 'dark' ? 'rgba(138,224,63,.24)' : 'rgba(138,224,63,.24)', 13 | redColor: this.option.theme === 'dark' ? 'rgb(209,29,32)' : 'rgb(209,29,32)', 14 | redBackgroundColor: this.option.theme === 'dark' ? 'rgba(209,29,32,.24)' : 'rgba(209,29,32,.24)', 15 | }; 16 | 17 | canvas.addEventListener('mousemove', e => { 18 | this.pos = this.getMousePos(e); 19 | this.forceUpdate(); 20 | }); 21 | this.forceUpdate(); 22 | this.draw(); 23 | } 24 | 25 | Depth.prototype.setOption = function(option) { 26 | if (this.option) { 27 | this.option = { 28 | theme: option.theme || this.option.theme, 29 | fontSize: option.fontSize || this.option.fontSize, 30 | priceDecimal: option.priceDecimal === undefined ? this.option.priceDecimal : option.priceDecimal, 31 | } 32 | } else { 33 | this.option = { 34 | theme: option.theme || 'dark', 35 | fontSize: option.fontSize || 14, 36 | priceDecimal: option.priceDecimal === undefined ? 0 : option.priceDecimal, 37 | } 38 | } 39 | this.ctx = this.canvas.getContext('2d'); 40 | this.ctx.font = this.dpr * this.option.fontSize + 'px Consolas, Monaco, monospace, sans-serif'; 41 | this.width = this.canvas.width; 42 | this.height = this.canvas.height; 43 | this.forceUpdate(); 44 | } 45 | 46 | Depth.prototype.forceUpdate = function() { 47 | this.force = true; 48 | } 49 | 50 | Depth.prototype.draw = function() { 51 | if (!this.force) { 52 | requestAnimationFrame(this.draw.bind(this)); 53 | return; 54 | } 55 | const bottom = 20 * this.dpr; 56 | const width = this.width; 57 | const height = this.height; 58 | const contentWidth = width; 59 | const contentHeight = height - bottom; 60 | const data = this.data; 61 | const ctx = this.ctx; 62 | ctx.clearRect(0, 0, width, height); 63 | 64 | const bidsPrice = []; 65 | const asksPrice = []; 66 | const bidsVolume = []; 67 | const asksVolume = []; 68 | data.bids.forEach(el => { 69 | bidsPrice.push(parseFloat(el[0])); 70 | bidsVolume.push(parseFloat(el[1])); 71 | }); 72 | data.asks.forEach(el => { 73 | asksPrice.push(parseFloat(el[0])); 74 | asksVolume.push(parseFloat(el[1])); 75 | }); 76 | 77 | const bidsDepth = []; 78 | for (let i = 0; i < bidsVolume.length; i++) { 79 | if (i === 0) { 80 | bidsDepth[i] = parseFloat(bidsVolume[i]); 81 | continue; 82 | } 83 | bidsDepth[i] = bidsDepth[i - 1] + parseFloat(bidsVolume[i]); 84 | } 85 | 86 | const asksDepth = []; 87 | for (let i = 0; i < asksVolume.length; i++) { 88 | if (i === 0) { 89 | asksDepth[i] = parseFloat(asksVolume[i]); 90 | continue; 91 | } 92 | asksDepth[i] = asksDepth[i - 1] + parseFloat(asksVolume[i]); 93 | } 94 | 95 | const maxVolume = Math.max(bidsDepth[bidsDepth.length - 1], asksDepth[asksDepth.length - 1]) * 1.2; 96 | let n = (maxVolume * 0.2).toFixed(0).length; 97 | const interval = Math.ceil(maxVolume * 0.2 / Math.pow(10, n - 1)) * Math.pow(10, n - 1); 98 | const yAxis = []; 99 | for (let i = interval; i < maxVolume; i += interval) { 100 | yAxis.unshift(i); 101 | } 102 | 103 | ctx.lineWidth = this.dpr; 104 | ctx.fillStyle = this.colors.background; 105 | ctx.fillRect(0, 0, width, height); 106 | 107 | // 买单 108 | const p1 = (bidsPrice[0] - bidsPrice[bidsPrice.length - 1]) / (asksPrice[asksPrice.length - 1] - bidsPrice[bidsPrice.length - 1]); 109 | ctx.lineWidth = this.dpr * 2; 110 | ctx.beginPath(); 111 | let lastX, lastY; 112 | for (let i = 0; i < bidsDepth.length; i++) { 113 | const p = (bidsPrice[i] - bidsPrice[bidsPrice.length - 1]) / (bidsPrice[0] - bidsPrice[bidsPrice.length - 1]); 114 | const x = contentWidth * p1 * p 115 | const y = contentHeight - bidsDepth[i] / maxVolume * contentHeight; 116 | if (i === 0) { 117 | ctx.moveTo(x, contentHeight); 118 | ctx.lineTo(x, y); 119 | } else { 120 | ctx.lineTo(x, lastY); 121 | ctx.lineTo(x, y); 122 | } 123 | lastX = contentWidth * p1 * p; 124 | lastY = contentHeight - bidsDepth[i] / maxVolume * contentHeight; 125 | } 126 | ctx.strokeStyle = this.colors.greenColor; 127 | ctx.stroke(); 128 | ctx.lineTo(0, contentHeight); 129 | ctx.closePath(); 130 | ctx.fillStyle = this.colors.greenBackgroundColor; 131 | ctx.fill(); 132 | 133 | ctx.lineWidth = this.dpr; 134 | ctx.textAlign = 'center'; 135 | ctx.textBaseline = 'middle'; 136 | for (let i = 0; i < bidsDepth.length; i++) { 137 | const p = (bidsPrice[i] - bidsPrice[bidsPrice.length - 1]) / (bidsPrice[0] - bidsPrice[bidsPrice.length - 1]); 138 | const x = contentWidth * p1 * p 139 | const y = contentHeight - bidsDepth[i] / maxVolume * contentHeight; 140 | if (this.pos && this.pos.x <= lastX && this.pos.x > x) { 141 | ctx.beginPath(); 142 | ctx.arc(this.pos.x, lastY, 4 * this.dpr, 0, Math.PI * 2, true); 143 | ctx.fillStyle = this.colors.greenColor; 144 | ctx.closePath(); 145 | ctx.fill(); 146 | 147 | ctx.fillStyle = this.colors.fontColor; 148 | if (lastY > contentHeight * 0.5) { 149 | ctx.strokeStyle = this.colors.splitColor; 150 | ctx.beginPath(); 151 | ctx.moveTo(this.pos.x, 0); 152 | ctx.lineTo(this.pos.x, (lastY - 10 * this.dpr) * 0.5 - 10 * this.dpr); 153 | ctx.stroke(); 154 | ctx.beginPath(); 155 | ctx.moveTo(this.pos.x, (lastY - 10 * this.dpr) * 0.5 + 10 * this.dpr); 156 | ctx.lineTo(this.pos.x, lastY - 10 * this.dpr); 157 | ctx.stroke(); 158 | ctx.fillText(bidsPrice[i], this.pos.x, (lastY - 10 * this.dpr) * 0.5); 159 | 160 | ctx.beginPath(); 161 | ctx.moveTo(this.pos.x, contentHeight); 162 | ctx.lineTo(this.pos.x, lastY + 10 * this.dpr > contentHeight ? contentHeight : lastY + 10 * this.dpr); 163 | ctx.strokeStyle = this.colors.greenColor; 164 | ctx.stroke(); 165 | } else { 166 | ctx.strokeStyle = this.colors.splitColor; 167 | ctx.beginPath(); 168 | ctx.moveTo(this.pos.x, 0); 169 | ctx.lineTo(this.pos.x, lastY - 10 * this.dpr); 170 | ctx.stroke(); 171 | 172 | let lineY = lastY + 10 * this.dpr > contentHeight ? contentHeight : lastY + 10 * this.dpr 173 | let textY = (contentHeight + lineY) * 0.5; 174 | ctx.beginPath(); 175 | ctx.strokeStyle = this.colors.greenColor; 176 | ctx.moveTo(this.pos.x, contentHeight); 177 | ctx.lineTo(this.pos.x, textY + 10 * this.dpr); 178 | ctx.stroke(); 179 | ctx.beginPath(); 180 | ctx.moveTo(this.pos.x, textY - 10 * this.dpr) 181 | ctx.lineTo(this.pos.x, lineY); 182 | ctx.stroke(); 183 | ctx.fillText(bidsPrice[i], this.pos.x, textY); 184 | } 185 | } 186 | lastX = contentWidth * p1 * p; 187 | lastY = contentHeight - bidsDepth[i] / maxVolume * contentHeight; 188 | } 189 | 190 | 191 | // 卖单 192 | const p2 = (asksPrice[asksPrice.length - 1] - asksPrice[0]) / (asksPrice[asksPrice.length - 1] - bidsPrice[bidsPrice.length - 1]); 193 | ctx.beginPath(); 194 | ctx.lineWidth = this.dpr * 2; 195 | for (let i = 0; i < asksPrice.length; i++) { 196 | const p = (asksPrice[i] - asksPrice[0]) / (asksPrice[asksPrice.length - 1] - asksPrice[0]); 197 | const x = contentWidth * (1 - p2) + contentWidth * p2 * p; 198 | const y = contentHeight - asksDepth[i] / maxVolume * contentHeight; 199 | if (i === 0) { 200 | ctx.moveTo(x, contentHeight); 201 | ctx.lineTo(x, y); 202 | } else { 203 | ctx.lineTo(x, lastY); 204 | ctx.lineTo(x, y); 205 | } 206 | lastX = contentWidth * (1 - p2) + contentWidth * p2 * p; 207 | lastY = contentHeight - asksDepth[i] / maxVolume * contentHeight; 208 | } 209 | ctx.strokeStyle = this.colors.redColor; 210 | ctx.stroke(); 211 | ctx.lineTo(contentWidth, contentHeight); 212 | ctx.closePath(); 213 | ctx.fillStyle = this.colors.redBackgroundColor; 214 | ctx.fill(); 215 | 216 | ctx.lineWidth = this.dpr; 217 | for (let i = 0; i < asksPrice.length; i++) { 218 | const p = (asksPrice[i] - asksPrice[0]) / (asksPrice[asksPrice.length - 1] - asksPrice[0]); 219 | const x = contentWidth * (1 - p2) + contentWidth * p2 * p; 220 | const y = contentHeight - asksDepth[i] / maxVolume * contentHeight; 221 | if (this.pos && this.pos.x >= lastX && this.pos.x < x) { 222 | ctx.beginPath(); 223 | ctx.arc(this.pos.x, lastY, 4 * this.dpr, 0, Math.PI * 2, true); 224 | ctx.fillStyle = this.colors.redColor; 225 | ctx.closePath(); 226 | ctx.fill(); 227 | 228 | ctx.fillStyle = this.colors.fontColor; 229 | if (lastY > contentHeight * 0.5) { 230 | ctx.strokeStyle = this.colors.splitColor; 231 | ctx.beginPath(); 232 | ctx.moveTo(this.pos.x, 0); 233 | ctx.lineTo(this.pos.x, (lastY - 10 * this.dpr) * 0.5 - 10 * this.dpr); 234 | ctx.stroke(); 235 | ctx.beginPath(); 236 | ctx.moveTo(this.pos.x, (lastY - 10 * this.dpr) * 0.5 + 10 * this.dpr); 237 | ctx.lineTo(this.pos.x, lastY - 10 * this.dpr); 238 | ctx.stroke(); 239 | ctx.fillText(asksPrice[i], this.pos.x, (lastY - 10 * this.dpr) * 0.5); 240 | 241 | ctx.beginPath(); 242 | ctx.moveTo(this.pos.x, contentHeight); 243 | ctx.lineTo(this.pos.x, lastY + 10 * this.dpr > contentHeight ? contentHeight : lastY + 10 * this.dpr); 244 | ctx.strokeStyle = this.colors.redColor; 245 | ctx.stroke(); 246 | } else { 247 | ctx.strokeStyle = this.colors.splitColor; 248 | ctx.beginPath(); 249 | ctx.moveTo(this.pos.x, 0); 250 | ctx.lineTo(this.pos.x, lastY - 10 * this.dpr); 251 | ctx.stroke(); 252 | 253 | let lineY = lastY + 10 * this.dpr > contentHeight ? contentHeight : lastY + 10 * this.dpr 254 | let textY = (contentHeight + lineY) * 0.5; 255 | ctx.beginPath(); 256 | ctx.strokeStyle = this.colors.redColor; 257 | ctx.moveTo(this.pos.x, contentHeight); 258 | ctx.lineTo(this.pos.x, textY + 10 * this.dpr); 259 | ctx.stroke(); 260 | ctx.beginPath(); 261 | ctx.moveTo(this.pos.x, textY - 10 * this.dpr) 262 | ctx.lineTo(this.pos.x, lineY); 263 | ctx.stroke(); 264 | ctx.fillText(asksPrice[i], this.pos.x, textY); 265 | } 266 | } 267 | lastX = contentWidth * (1 - p2) + contentWidth * p2 * p; 268 | lastY = contentHeight - asksDepth[i] / maxVolume * contentHeight; 269 | } 270 | 271 | 272 | // y轴刻度 273 | ctx.textAlign = 'left'; 274 | ctx.textBaseline = 'middle'; 275 | ctx.fillStyle = this.colors.fontColor; 276 | ctx.strokeStyle = this.colors.splitColor; 277 | for (let i = interval; i < maxVolume; i += interval) { 278 | let y = contentHeight - contentHeight * i / maxVolume; 279 | ctx.fillText(i, 12 * this.dpr, y); 280 | ctx.beginPath(); 281 | ctx.moveTo(0, y); 282 | ctx.lineTo(8 * this.dpr, y); 283 | ctx.stroke(); 284 | } 285 | ctx.textAlign = 'right'; 286 | for (let i = interval; i < maxVolume; i += interval) { 287 | let y = contentHeight - contentHeight * i / maxVolume; 288 | ctx.fillText(i, width - 12 * this.dpr, y); 289 | ctx.beginPath(); 290 | ctx.moveTo(width, y); 291 | ctx.lineTo(width - 8 * this.dpr, y); 292 | ctx.stroke(); 293 | } 294 | 295 | n = 0; 296 | if ((asksPrice[asksPrice.length - 1] - bidsPrice[bidsPrice.length - 1]) >= 1) { 297 | n = (asksPrice[asksPrice.length - 1] - bidsPrice[bidsPrice.length - 1]).toFixed(0).length; 298 | } else { 299 | if ((asksPrice[asksPrice.length - 1] - bidsPrice[bidsPrice.length - 1]) < 0.000001) { 300 | let str = ((asksPrice[asksPrice.length - 1] - bidsPrice[bidsPrice.length - 1]) * 100000).toString().split('.')[1]; 301 | for (let i = 0; i < str.length; i++) { 302 | if (str.charAt(i) == 0) { 303 | n--; 304 | } 305 | } 306 | n -= 5; 307 | } else { 308 | let str = (asksPrice[asksPrice.length - 1] - bidsPrice[bidsPrice.length - 1]).toString().split('.')[1]; 309 | for (let i = 0; i < str.length; i++) { 310 | if (str.charAt(i) == 0) { 311 | n--; 312 | } 313 | } 314 | } 315 | } 316 | let number = 0.15; 317 | const intervalX = Math.ceil((asksPrice[asksPrice.length - 1] - bidsPrice[bidsPrice.length - 1]) * number / Math.pow(10, n - 2)) * Math.pow(10, n - 2); 318 | ctx.fillStyle = this.colors.fontColor; 319 | ctx.textAlign = 'center'; 320 | ctx.textBaseline = 'top'; 321 | for (let i = bidsPrice[bidsPrice.length - 1] + intervalX; i < asksPrice[asksPrice.length - 1]; i += intervalX) { 322 | ctx.fillText(i.toFixed(this.option.priceDecimal || 2), (i - bidsPrice[bidsPrice.length - 1]) / (asksPrice[asksPrice.length - 1] - bidsPrice[bidsPrice.length - 1]) * contentWidth, contentHeight); 323 | } 324 | 325 | if (this.force) { 326 | this.force = false; 327 | } 328 | requestAnimationFrame(this.draw.bind(this)); 329 | }; 330 | -------------------------------------------------------------------------------- /src2/KLine.js: -------------------------------------------------------------------------------- 1 | import setOption from './setOption'; 2 | import setData from './setData'; 3 | import draw from './draw'; 4 | import drawMain from './drawMain'; 5 | import drawAid from './drawAid'; 6 | import drawTimeline from './drawTimeline'; 7 | import operation, { drawHairLine } from './operation'; 8 | import select from './select'; 9 | import { moveRange, scaleRange } from './range'; 10 | import computAxis from './computAxis'; 11 | import canDraw from './canDraw'; 12 | import Depth from './Depth'; 13 | import Depth2 from './Depth2'; 14 | import drawLines from './tools/drawLines'; 15 | import drawLineCache from './tools/drawLineCache'; 16 | import ParallelSegment from './tools/ParallelSegment'; 17 | import HorizontalLine from './tools/HorizontalLine'; 18 | import HorizontalBeam from './tools/HorizontalBeam'; 19 | import VerticalLine from './tools/VerticalLine'; 20 | import PriceLine from './tools/PriceLine'; 21 | import Segment from './tools/Segment'; 22 | import Line from './tools/Line'; 23 | import Beam from './tools/Beam'; 24 | import Arrow from './tools/Arrow'; 25 | 26 | export function KLine(canvas, overCanvas, option) { 27 | this.canvas = canvas; 28 | this.overCanvas = overCanvas; 29 | if (canvas.width !== overCanvas.width || canvas.height !== overCanvas.height) { 30 | console.log('Two canvas\'s width and height must equal'); 31 | return; 32 | } 33 | this.dpr = canvas.width / canvas.getBoundingClientRect().width; 34 | this.setOption(option); 35 | this.draw(); 36 | this.operation(canvas, overCanvas); 37 | } 38 | 39 | KLine.prototype = { 40 | setOption, 41 | setData, 42 | draw, 43 | drawMain, 44 | drawAid, 45 | drawHairLine, 46 | drawTimeline, 47 | drawLines, 48 | drawLineCache, 49 | operation, 50 | select, 51 | getMousePos, 52 | setDP, 53 | isInLineView, 54 | moveRange, 55 | scaleRange, 56 | canDraw, 57 | computAxis, 58 | forceUpdate: function(canvasCanDraw, overCanvasCanDraw) { 59 | this.force = [canvasCanDraw || this.force[0], overCanvasCanDraw || this.force[1]]; 60 | }, 61 | string: function(num) { 62 | if (Math.abs(num) > 0.000001) { 63 | return num; 64 | } 65 | let length = num.toFixed(20).match(/([1-9]*)(0*)$/)[2].length; 66 | return num.toFixed(20 - length); 67 | }, 68 | beginDrawLine: function(type) { 69 | if (type === 'parallelsegment') { 70 | this.lineCache = new ParallelSegment(this.overCtx, this.colors, this); 71 | } else if (type === 'horizontalline') { 72 | this.lineCache = new HorizontalLine(this.overCtx, this.colors, this); 73 | } else if (type === 'horizontalbeam') { 74 | this.lineCache = new HorizontalBeam(this.overCtx, this.colors, this); 75 | } else if (type === 'verticalline') { 76 | this.lineCache = new VerticalLine(this.overCtx, this.colors, this); 77 | } else if (type === 'priceline') { 78 | this.lineCache = new PriceLine(this.overCtx, this.colors, this); 79 | } else if (type === 'segment') { 80 | this.lineCache = new Segment(this.overCtx, this.colors, this); 81 | } else if (type === 'line') { 82 | this.lineCache = new Line(this.overCtx, this.colors, this); 83 | } else if (type === 'beam') { 84 | this.lineCache = new Beam(this.overCtx, this.colors, this); 85 | } else if (type === 'arrow') { 86 | this.lineCache = new Arrow(this.overCtx, this.colors, this); 87 | } 88 | }, 89 | clearLine: function(index) { 90 | this.lines.splice(index, 1); 91 | }, 92 | clearAllLine: function() { 93 | this.lines = []; 94 | } 95 | }; 96 | 97 | // 获取鼠标在canvas上的坐标点 98 | function getMousePos(e) { 99 | let rect = e.target.getBoundingClientRect(); 100 | return { 101 | x: (e.clientX - rect.left) * this.dpr, 102 | y: (e.clientY - rect.top) * this.dpr 103 | }; 104 | } 105 | 106 | // 控制小数位数 107 | function setDP(num, priceDecimal) { 108 | if (priceDecimal) { 109 | return parseFloat(num.toFixed(priceDecimal)); 110 | } 111 | let n = /(\d*).(0*)(\d*)$/.exec(num.toFixed(20))[2].length; 112 | if (n > 17) { 113 | return parseFloat(num.toFixed(20)); 114 | } else { 115 | return parseFloat(num.toFixed(n + 3)); 116 | } 117 | } 118 | 119 | // 判断鼠标是否在${this.views}中 120 | function isInLineView(pos) { 121 | const { x, y } = pos; 122 | const view1 = this.mainView; 123 | const view2 = this.aidView; 124 | if (x >= view1.x && x < view1.x + view1.w && y >= view1.y && y < view1.y + view1.h) { 125 | return view1; 126 | } else if (x >= view2.x && x < view2.x + view2.w && y >= view2.y && y < view2.y + view2.h) { 127 | return view2; 128 | } else { 129 | return false; 130 | } 131 | } 132 | 133 | Depth.prototype.getMousePos = getMousePos; 134 | Depth.prototype.setDP = setDP; 135 | Depth2.prototype.getMousePos = getMousePos; 136 | Depth2.prototype.setDP = setDP; 137 | 138 | export { 139 | Depth, 140 | Depth2, 141 | }; 142 | -------------------------------------------------------------------------------- /src2/canDraw.js: -------------------------------------------------------------------------------- 1 | export default function canDraw() { 2 | if (this.state.range[0] != this.lastState.range[0] || this.state.range[1] != this.lastState.range[1]) { 3 | return [true, true]; 4 | } 5 | if (this.force[0] || this.force[1]) { 6 | const temp = this.force; 7 | this.force = [false, false]; 8 | return temp; 9 | } 10 | return [false, false]; 11 | } 12 | -------------------------------------------------------------------------------- /src2/computAxis.js: -------------------------------------------------------------------------------- 1 | // 计算最大价格,最小价格,y轴显示的价格差 2 | export default function computAxis() { 3 | const start = this.state.start; 4 | const hi = this.state.hi; 5 | const lo = this.state.lo; 6 | const close = this.state.close; 7 | const ma30 = this.state.ma30; 8 | const ma7 = this.state.ma7; 9 | const ema30 = this.state.ema30; 10 | const ema7 = this.state.ema7; 11 | const up = this.state.up; 12 | const mb = this.state.mb; 13 | const dn = this.state.dn; 14 | const startIndex = this.state.range[0]; 15 | const endIndex = this.state.range[1]; 16 | let maxY = Math.max(start[startIndex], hi[startIndex], lo[startIndex], close[startIndex], ma30[startIndex], ma7[startIndex], ema30[startIndex], ema7[startIndex]); 17 | let minY = Math.min(start[startIndex], hi[startIndex], lo[startIndex], close[startIndex], ma30[startIndex], ma7[startIndex], ema30[startIndex], ema7[startIndex]); 18 | let maxPrice = hi[startIndex]; 19 | let minPrice = lo[startIndex]; 20 | let maxPriceIndex = startIndex; 21 | let minPriceIndex = startIndex; 22 | let mainCsi = this.option.mainCsi; 23 | for (let i = startIndex; i < endIndex; i++) { 24 | if (i >= this.state.times.length) { 25 | break; 26 | } 27 | let csi = []; 28 | if (mainCsi === 'ma') { 29 | csi = [ma30[i], ma7[i]]; 30 | } else if (mainCsi === 'ema') { 31 | csi = [ema30[i], ema7[i]]; 32 | } else if (mainCsi === 'boll') { 33 | csi = [up[i], mb[i], dn[i]]; 34 | } 35 | let maxVal = Math.max(start[i], hi[i], lo[i], close[i], ...csi); 36 | let minVal = Math.min(start[i], hi[i], lo[i], close[i], ...csi); 37 | maxY = maxVal > maxY ? maxVal : maxY; 38 | minY = minVal < minY ? minVal : minY; 39 | let maxPriceVal = hi[i]; 40 | let minPriceVal = lo[i]; 41 | if (maxPriceVal > maxPrice) { 42 | maxPriceIndex = i; 43 | maxPrice = maxPriceVal; 44 | } 45 | if (minPriceVal < minPrice) { 46 | minPriceIndex = i; 47 | minPrice = minPriceVal; 48 | } 49 | } 50 | let cha = maxY - minY; 51 | let n = 0; 52 | if (cha >= 1) { 53 | n = cha.toFixed(0).length; 54 | } else { 55 | if (cha < 0.000001) { 56 | let str = (cha * 100000).toString().split('.')[1] || ''; 57 | for (let i = 0; i < str.length; i++) { 58 | if (str.charAt(i) == 0) { 59 | n--; 60 | } 61 | } 62 | n -= 5; 63 | } else { 64 | let str = cha.toString().split('.')[1] || ''; 65 | for (let i = 0; i < str.length; i++) { 66 | if (str.charAt(i) == 0) { 67 | n--; 68 | } 69 | } 70 | } 71 | } 72 | const intervalY = Math.ceil((maxY - minY) * 0.2 / Math.pow(10, n - 2)) * Math.pow(10, n - 2); 73 | return { 74 | maxY, 75 | minY, 76 | maxPrice, 77 | maxPriceIndex, 78 | minPrice, 79 | minPriceIndex, 80 | max: maxY + intervalY - maxY % intervalY, 81 | min: minY - minY % intervalY, 82 | intervalY, 83 | }; 84 | } 85 | -------------------------------------------------------------------------------- /src2/draw.js: -------------------------------------------------------------------------------- 1 | export default function draw() { 2 | if (!this.lastState) { 3 | this.lastState = {range: [-1, -1]}; 4 | } 5 | const canDraw = this.canDraw(); 6 | if (canDraw[1]) { 7 | const overCtx = this.overCtx; 8 | overCtx.clearRect(0, 0, this.width, this.height); 9 | this.drawHairLine(); 10 | if (this.lineCache) { 11 | this.drawLineCache(); 12 | } 13 | this.drawLines(); 14 | } 15 | if (canDraw[0]) { 16 | const ctx = this.ctx; 17 | ctx.clearRect(0, 0, this.width, this.height); 18 | 19 | drawBackground.call(this); 20 | drawTime.call(this); 21 | drawSplitLine.call(this); 22 | 23 | const yaxis = this.computAxis(); 24 | 25 | this.drawMain(yaxis); 26 | 27 | this.drawAid(); 28 | } 29 | 30 | this.lastState = this.state; 31 | 32 | requestAnimationFrame(this.draw.bind(this)); 33 | } 34 | 35 | function drawBackground() { 36 | const ctx = this.ctx; 37 | ctx.fillStyle = this.colors.background; 38 | ctx.fillRect(0, 0, this.width, this.height); 39 | ctx.strokeStyle = this.colors.splitLine; 40 | ctx.strokeRect(0, 0, this.width, this.height); 41 | } 42 | 43 | function drawTime() { 44 | const ctx = this.ctx; 45 | ctx.strokeStyle = this.colors.splitLine; 46 | ctx.strokeRect(-this.dpr, this.timeView.y, this.timeView.w + this.dpr, this.timeView.h); 47 | } 48 | 49 | function drawSplitLine() { 50 | const ctx = this.ctx; 51 | ctx.strokeStyle = this.colors.splitLine; 52 | ctx.beginPath(); 53 | ctx.moveTo(0, (this.mainView.h + this.mainView.y + this.aidView.y) * 0.5); 54 | ctx.lineTo(this.width, (this.mainView.h + this.mainView.y + this.aidView.y) * 0.5); 55 | ctx.stroke(); 56 | 57 | ctx.beginPath(); 58 | ctx.moveTo(this.mainYaxisView.x, 0); 59 | ctx.lineTo(this.aidYaxisView.x, this.aidYaxisView.y + this.aidYaxisView.h); 60 | ctx.stroke(); 61 | } 62 | -------------------------------------------------------------------------------- /src2/drawAid.js: -------------------------------------------------------------------------------- 1 | function toInt(num) { 2 | return ~~(0.5 + num); 3 | } 4 | export default function drawAid() { 5 | if (this.option.aidCsi === 'volume') { 6 | drawVolume.call(this); 7 | } else if (this.option.aidCsi === 'macd') { 8 | drawMacd.call(this); 9 | } else if (this.option.aidCsi === 'kdj') { 10 | drawKdj.call(this); 11 | } 12 | } 13 | 14 | function drawVolume() { 15 | const ctx = this.ctx; 16 | const aidView = this.aidView; 17 | const aidYaxisView = this.aidYaxisView; 18 | const [startIndex, endIndex] = this.state.range; 19 | const verticalRectNumber = endIndex - startIndex; 20 | 21 | const realVolume = []; 22 | const realVolumeMa7 = []; 23 | const realVolumeMa30 = []; 24 | this.state.volume.forEach((el, i) => { 25 | if (i >= startIndex && i < endIndex) { 26 | realVolume.push(el); 27 | realVolumeMa7.push(this.state.volumeMa7[i]); 28 | realVolumeMa30.push(this.state.volumeMa30[i]); 29 | } 30 | }); 31 | const maxVolume = Math.max(...realVolume, ...realVolumeMa7, ...realVolumeMa30) * 1.25; 32 | this.csiYaxisSector = [maxVolume, 0]; 33 | 34 | let n = 0; 35 | if (maxVolume >= 1) { 36 | n = maxVolume.toFixed(0).length; 37 | } else { 38 | if (maxVolume < 0.000001) { 39 | let str = (maxVolume * 100000).toString().split('.')[1]; 40 | for (let i = 0; i < str.length; i++) { 41 | if (str.charAt(i) == 0) { 42 | n--; 43 | } 44 | } 45 | n -= 5; 46 | } else { 47 | let str = maxVolume.toString().split('.')[1]; 48 | for (let i = 0; i < str.length; i++) { 49 | if (str.charAt(i) == 0) { 50 | n--; 51 | } 52 | } 53 | } 54 | } 55 | const interval = Math.ceil(maxVolume * 0.25 / Math.pow(10, n - 2)) * Math.pow(10, n - 2); 56 | const yAxis = []; 57 | for (let i = interval; i < maxVolume; i += interval) { 58 | yAxis.unshift(i); 59 | } 60 | 61 | ctx.textAlign = 'center'; 62 | ctx.textBaseline = 'middle'; 63 | ctx.fillStyle = this.colors.textColor; 64 | // ctx.setLineDash([2 * this.dpr], 2 * this.dpr); 65 | ctx.strokeStyle = this.colors.splitLine; 66 | ctx.lineWidth = this.dpr * 0.5; 67 | for (let i = 0; i < yAxis.length; i++) { 68 | ctx.fillText(yAxis[i], aidYaxisView.x + aidYaxisView.w * 0.5, aidYaxisView.y + aidYaxisView.h - yAxis[i] / maxVolume * aidYaxisView.h); 69 | // ctx.beginPath(); 70 | // ctx.moveTo(0, aidYaxisView.y + aidYaxisView.h - yAxis[i] / maxVolume * aidYaxisView.h); 71 | // ctx.lineTo(aidYaxisView.x, aidYaxisView.y + aidYaxisView.h - yAxis[i] / maxVolume * aidYaxisView.h); 72 | // ctx.stroke(); 73 | } 74 | 75 | // ctx.setLineDash([]); 76 | ctx.lineWidth = this.dpr; 77 | ctx.strokeStyle = this.colors.textColor; 78 | for (let i = 0; i < yAxis.length; i++) { 79 | let x = aidYaxisView.x; 80 | let y = aidYaxisView.y + aidYaxisView.h - yAxis[i] / maxVolume * aidYaxisView.h; 81 | ctx.beginPath(); 82 | ctx.moveTo(x, y); 83 | ctx.lineTo(x + 10, y); 84 | ctx.stroke(); 85 | } 86 | 87 | ctx.fillStyle = this.colors.greenColor; 88 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 89 | if (i >= this.state.times.length) { 90 | break; 91 | } 92 | if (this.state.start[i] < this.state.close[i]) { 93 | let x = (j + 0.1) * aidView.w / verticalRectNumber + aidView.x; 94 | let w = aidView.w / verticalRectNumber * 0.8; 95 | let h = -realVolume[j] / maxVolume * aidView.h; 96 | let y = aidView.y + aidView.h; 97 | ctx.fillRect(x, y, w, h); 98 | } 99 | } 100 | 101 | ctx.fillStyle = this.colors.redColor; 102 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 103 | if (i >= this.state.times.length) { 104 | break; 105 | } 106 | if (this.state.close[i] <= this.state.start[i]) { 107 | let x = (j + 0.1) * aidView.w / verticalRectNumber + aidView.x; 108 | let w = aidView.w / verticalRectNumber * 0.8; 109 | let h = -realVolume[j] / maxVolume * aidView.h; 110 | let y = aidView.y + aidView.h; 111 | ctx.fillRect(x, y, w, h); 112 | } 113 | } 114 | ctx.beginPath(); 115 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 116 | if (i >= this.state.times.length) { 117 | break; 118 | } 119 | ctx.strokeStyle = this.colors.ma30Color; 120 | let x = j * aidView.w / verticalRectNumber + 0.5 * aidView.w / verticalRectNumber + aidView.x; 121 | let y = (maxVolume - this.state.volumeMa30[i]) / maxVolume * aidView.h + aidView.y; 122 | if (j == 0) { 123 | ctx.moveTo(x, y); 124 | } 125 | ctx.lineTo(x, y); 126 | } 127 | ctx.stroke(); 128 | 129 | ctx.beginPath(); 130 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 131 | if (i >= this.state.times.length) { 132 | break; 133 | } 134 | ctx.strokeStyle = this.colors.ma7Color; 135 | let x = j * aidView.w / verticalRectNumber + 0.5 * aidView.w / verticalRectNumber + aidView.x; 136 | let y = (maxVolume - this.state.volumeMa7[i]) / maxVolume * aidView.h + aidView.y; 137 | if (j == 0) { 138 | ctx.moveTo(x, y); 139 | } 140 | ctx.lineTo(x, y); 141 | } 142 | ctx.stroke(); 143 | ctx.closePath(); 144 | } 145 | 146 | function drawMacd() { 147 | const ctx = this.ctx; 148 | const [startIndex, endIndex] = this.state.range; 149 | const verticalRectNumber = endIndex - startIndex; 150 | const aidView = this.aidView; 151 | const aidYaxisView = this.aidYaxisView; 152 | 153 | let max = 0; 154 | let min = 0; 155 | this.state.macd.forEach((el, i) => { 156 | if (i < startIndex || i >= endIndex) { 157 | return; 158 | } 159 | let val = Math.max(el, this.state.dif[i], this.state.dea[i]); 160 | max = max > val ? max : val; 161 | val = Math.min(el, this.state.dif[i], this.state.dea[i]); 162 | min = min < val ? min : val; 163 | }); 164 | max = (max > Math.abs(min) ? max : Math.abs(min)) * 1.25; 165 | this.csiYaxisSector = [max, -max]; 166 | const yAxis = [max, max * 2 / 3, max / 3, -max / 3, -max * 2 / 3, -max]; 167 | 168 | ctx.textAlign = 'center'; 169 | ctx.textBaseline = 'middle'; 170 | ctx.fillStyle = this.colors.textColor; 171 | // ctx.setLineDash([2 * this.dpr], 2 * this.dpr); 172 | ctx.strokeStyle = this.colors.splitLine; 173 | ctx.lineWidth = this.dpr * 0.5; 174 | for (let i = 1; i < yAxis.length - 1; i++) { 175 | ctx.fillText(this.string(this.setDP(yAxis[i])), aidYaxisView.x + aidYaxisView.w * 0.5, aidYaxisView.y + i / (yAxis.length - 1) * aidYaxisView.h); 176 | // ctx.beginPath(); 177 | // ctx.moveTo(0, aidYaxisView.y + i / (yAxis.length - 1) * aidYaxisView.h); 178 | // ctx.lineTo(aidYaxisView.x, aidYaxisView.y + i / (yAxis.length - 1) * aidYaxisView.h); 179 | // ctx.stroke(); 180 | } 181 | 182 | // ctx.setLineDash([]); 183 | ctx.lineWidth = this.dpr; 184 | ctx.fillStyle = this.colors.greenColor; 185 | ctx.strokeStyle = this.colors.greenColor; 186 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 187 | if (i >= this.state.times.length) { 188 | break; 189 | } 190 | if (this.state.macd[i] > 0) { 191 | let y = aidView.y + aidView.h * 0.5; 192 | let w = aidView.w / verticalRectNumber * 0.8; 193 | let x = j * aidView.w / verticalRectNumber + aidView.x + w * 0.1; 194 | let h = -this.state.macd[i] / max * aidView.h * 0.5; 195 | if (Math.abs(this.state.macd[i]) > Math.abs(this.state.macd[i - 1])) { 196 | ctx.fillRect(x, y, w, h); 197 | } else { 198 | if (w <= this.dpr * 4) { 199 | ctx.fillRect(x, y, w, h); 200 | } else { 201 | ctx.strokeRect(x, y, w, h); 202 | } 203 | } 204 | } 205 | } 206 | ctx.fillStyle = this.colors.redColor; 207 | ctx.strokeStyle = this.colors.redColor; 208 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 209 | if (i >= this.state.times.length) { 210 | break; 211 | } 212 | if (this.state.macd[i] <= 0) { 213 | let y = aidView.y + aidView.h * 0.5; 214 | let w = aidView.w / verticalRectNumber * 0.8; 215 | let x = j * aidView.w / verticalRectNumber + aidView.x + w * 0.1; 216 | let h = -this.state.macd[i] / max * aidView.h * 0.5; 217 | if (Math.abs(this.state.macd[i]) > Math.abs(this.state.macd[i - 1])) { 218 | ctx.fillRect(x, y, w, h); 219 | } else { 220 | if (w <= this.dpr * 4) { 221 | ctx.fillRect(x, y, w, h); 222 | } else { 223 | ctx.strokeRect(x, y, w, h); 224 | } 225 | } 226 | } 227 | } 228 | 229 | // dif 230 | ctx.strokeStyle = this.colors.ma7Color; 231 | ctx.beginPath(); 232 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 233 | if (i >= this.state.times.length) { 234 | break; 235 | } 236 | let x = j * aidView.w / verticalRectNumber + 0.5 * aidView.w / verticalRectNumber + aidView.x; 237 | let y = (max - this.state.dif[i]) / (2 * max) * aidView.h + aidView.y; 238 | if (j === 0) { 239 | ctx.moveTo(x, y); 240 | continue; 241 | } 242 | ctx.lineTo(x, y); 243 | } 244 | ctx.stroke(); 245 | 246 | // dea 247 | ctx.strokeStyle = this.colors.ma30Color; 248 | ctx.beginPath(); 249 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 250 | if (i >= this.state.times.length) { 251 | break; 252 | } 253 | let x = j * aidView.w / verticalRectNumber + 0.5 * aidView.w / verticalRectNumber + aidView.x; 254 | let y = (max - this.state.dea[i]) / (2 * max) * aidView.h + aidView.y; 255 | if (j === 0) { 256 | ctx.moveTo(x, y); 257 | continue; 258 | } 259 | ctx.lineTo(x, y); 260 | } 261 | ctx.stroke(); 262 | } 263 | 264 | function drawKdj() { 265 | const ctx = this.ctx; 266 | const [startIndex, endIndex] = this.state.range; 267 | const verticalRectNumber = endIndex - startIndex; 268 | const aidView = this.aidView; 269 | const aidYaxisView = this.aidYaxisView; 270 | 271 | let max = 0; 272 | let min = 0; 273 | this.state.k.forEach((el, i) => { 274 | if (i < startIndex || i >= endIndex) { 275 | return; 276 | } 277 | let val = Math.max(el, this.state.d[i], this.state.j[i]); 278 | max = max > val ? max : val; 279 | val = Math.min(el, this.state.d[i], this.state.j[i]); 280 | min = min < val ? min : val; 281 | }); 282 | this.csiYaxisSector = [max, min]; 283 | 284 | max *= 1.1; 285 | const cha = max - min; 286 | 287 | let n = 0; 288 | if (cha >= 1) { 289 | n = cha.toFixed(0).length; 290 | } else { 291 | if (cha < 0.000001) { 292 | let str = (cha * 100000).toString().split('.')[1] || ''; 293 | for (let i = 0; i < str.length; i++) { 294 | if (str.charAt(i) == 0) { 295 | n--; 296 | } 297 | } 298 | n -= 5; 299 | } else { 300 | let str = cha.toString().split('.')[1] || ''; 301 | for (let i = 0; i < str.length; i++) { 302 | if (str.charAt(i) == 0) { 303 | n--; 304 | } 305 | } 306 | } 307 | } 308 | const interval = Math.ceil(cha * 0.25 / Math.pow(10, n - 2)) * Math.pow(10, n - 2); 309 | const yAxis = []; 310 | for (let i = 0; i < max; i += interval) { 311 | yAxis.unshift(i); 312 | } 313 | 314 | ctx.textAlign = 'center'; 315 | ctx.textBaseline = 'middle'; 316 | ctx.fillStyle = this.colors.textColor; 317 | // ctx.setLineDash([2 * this.dpr], 2 * this.dpr); 318 | ctx.strokeStyle = this.colors.splitLine; 319 | ctx.lineWidth = this.dpr * 0.5; 320 | for (let i = 0; i < yAxis.length; i++) { 321 | ctx.fillText(yAxis[i], aidYaxisView.x + aidYaxisView.w * 0.5, aidYaxisView.y + (max - yAxis[i]) / cha * aidYaxisView.h); 322 | // ctx.beginPath(); 323 | // ctx.moveTo(0, aidYaxisView.y + (max - yAxis[i]) / cha * aidYaxisView.h); 324 | // ctx.lineTo(aidYaxisView.x, aidYaxisView.y + (max - yAxis[i]) / cha * aidYaxisView.h); 325 | // ctx.stroke(); 326 | } 327 | 328 | // ctx.setLineDash([]); 329 | ctx.lineWidth = this.dpr; 330 | ctx.strokeStyle = this.colors.textColor; 331 | for (let i = 0; i < yAxis.length; i++) { 332 | let x = aidYaxisView.x; 333 | let y = aidYaxisView.y + (max - yAxis[i]) / cha * aidYaxisView.h; 334 | ctx.beginPath(); 335 | ctx.moveTo(x, y); 336 | ctx.lineTo(x + 10, y); 337 | ctx.stroke(); 338 | } 339 | 340 | ctx.strokeStyle = this.colors.ma7Color; 341 | ctx.beginPath(); 342 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 343 | if (i >= this.state.times.length) { 344 | break; 345 | } 346 | let x = j * aidView.w / verticalRectNumber + 0.5 * aidView.w / verticalRectNumber + aidView.x; 347 | let y = (max - this.state.k[i]) / cha * aidView.h + aidView.y; 348 | if (j == 0) { 349 | ctx.moveTo(x, y); 350 | } 351 | ctx.lineTo(x, y); 352 | } 353 | ctx.stroke(); 354 | 355 | ctx.strokeStyle = this.colors.ma30Color; 356 | ctx.beginPath(); 357 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 358 | if (i >= this.state.times.length) { 359 | break; 360 | } 361 | let x = j * aidView.w / verticalRectNumber + 0.5 * aidView.w / verticalRectNumber + aidView.x; 362 | let y = (max - this.state.d[i]) / cha * aidView.h + aidView.y; 363 | if (j == 0) { 364 | ctx.moveTo(x, y); 365 | } 366 | ctx.lineTo(x, y); 367 | } 368 | ctx.stroke(); 369 | 370 | ctx.strokeStyle = this.colors.macdColor; 371 | ctx.beginPath(); 372 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 373 | if (i >= this.state.times.length) { 374 | break; 375 | } 376 | let x = j * aidView.w / verticalRectNumber + 0.5 * aidView.w / verticalRectNumber + aidView.x; 377 | let y = (max - this.state.j[i]) / cha * aidView.h + aidView.y; 378 | if (j == 0) { 379 | ctx.moveTo(x, y); 380 | } 381 | ctx.lineTo(x, y); 382 | } 383 | ctx.stroke(); 384 | } 385 | -------------------------------------------------------------------------------- /src2/drawMain.js: -------------------------------------------------------------------------------- 1 | function toInt(num) { 2 | return ~~(0.5 + num); 3 | } 4 | export default function drawMain(yaxis) { 5 | const ctx = this.ctx; 6 | 7 | const times = this.state.times; 8 | const timeStr = this.state.timeStr; 9 | const start = this.state.start; 10 | const hi = this.state.hi; 11 | const lo = this.state.lo; 12 | const close = this.state.close; 13 | 14 | 15 | const { max, min, maxPrice, maxPriceIndex, minPrice, minPriceIndex, intervalY } = yaxis; 16 | 17 | const mainView = this.mainView; 18 | const mainYaxisView = this.mainYaxisView; 19 | const timeView = this.timeView; 20 | 21 | const [startIndex, endIndex] = this.state.range; 22 | const verticalRectNumber = endIndex - startIndex; 23 | 24 | // y轴刻度数值 y轴刻度线 25 | ctx.fillStyle = this.colors.textColor; 26 | ctx.strokeStyle = this.colors.splitLine; 27 | ctx.lineWidth = this.dpr * 0.5; 28 | // ctx.setLineDash([2 * this.dpr], 2 * this.dpr); 29 | ctx.textAlign = 'center'; 30 | ctx.textBaseline = 'middle'; 31 | let lengthY = (max - min) / intervalY; 32 | for (let i = 0; i < lengthY; i++) { 33 | ctx.fillText((max - (i * intervalY)).toFixed(this.option.priceDecimal), toInt(mainYaxisView.x + mainYaxisView.w * 0.5), toInt(i * intervalY / (max - min) * mainYaxisView.h + mainYaxisView.y)); 34 | 35 | let x = mainYaxisView.x; 36 | let y = i * intervalY / (max - min) * mainYaxisView.h + mainYaxisView.y; 37 | x = toInt(x); 38 | y = toInt(y); 39 | // ctx.beginPath(); 40 | // ctx.moveTo(0, y); 41 | // ctx.lineTo(x, y); 42 | // ctx.stroke(); 43 | } 44 | ctx.lineWidth = this.dpr; 45 | // ctx.setLineDash([]); 46 | ctx.strokeStyle = this.colors.textColor; 47 | for (let i = 0; i < lengthY; i++) { 48 | let x = mainYaxisView.x; 49 | let y = i * intervalY / (max - min) * mainYaxisView.h + mainYaxisView.y; 50 | x = toInt(x); 51 | y = toInt(y); 52 | ctx.beginPath(); 53 | ctx.moveTo(x + 5 * this.dpr, y); 54 | ctx.lineTo(x, y); 55 | ctx.stroke(); 56 | ctx.beginPath(); 57 | ctx.moveTo(this.width, y); 58 | ctx.lineTo(this.width - 5 * this.dpr, y); 59 | ctx.stroke(); 60 | } 61 | 62 | this.drawTimeline(); 63 | 64 | // 蜡烛线 65 | if (this.option.type === 'candle') { 66 | ctx.strokeStyle = this.colors.redColor; 67 | ctx.fillStyle = this.colors.redColor; 68 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 69 | if (i >= times.length) { 70 | break; 71 | } 72 | if (close[i] > start[i]) { 73 | continue; 74 | } 75 | let x = (j + 0.1) * mainView.w / verticalRectNumber + mainView.x; 76 | let y = (max - Math.max(start[i], close[i])) / (max - min) * mainView.h + mainView.y; 77 | let w = mainView.w / verticalRectNumber * 0.8; 78 | let h = (Math.max(start[i], close[i]) - Math.min(start[i], close[i])) / (max - min) * mainView.h; 79 | x = toInt(x); 80 | y = toInt(y); 81 | w = toInt(w); 82 | h = toInt(h); 83 | ctx.fillRect(x, y, w, h < this.dpr ? this.dpr : h); 84 | let x1 = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 85 | let y1 = (max - hi[i]) / (max - min) * mainView.h + mainView.y; 86 | let x2 = x1; 87 | let y2 = (max - lo[i]) / (max - min) * mainView.h + mainView.y; 88 | x1 = toInt(x1); 89 | y1 = toInt(y1); 90 | x2 = toInt(x2); 91 | y2 = toInt(y2); 92 | ctx.beginPath(); 93 | ctx.moveTo(x1, y1); 94 | ctx.lineTo(x2, y2); 95 | ctx.stroke(); 96 | } 97 | ctx.strokeStyle = this.colors.greenColor; 98 | ctx.fillStyle = this.colors.greenColor; 99 | for (let i = startIndex, j = 0; i < endIndex; i++, j++) { 100 | if (i >= times.length) { 101 | break; 102 | } 103 | if (close[i] <= start[i]) { 104 | continue; 105 | } 106 | let x = (j + 0.1) * mainView.w / verticalRectNumber + mainView.x; 107 | let y = (max - Math.max(start[i], close[i])) / (max - min) * mainView.h + mainView.y; 108 | let w = mainView.w / verticalRectNumber * 0.8; 109 | let h = (Math.max(start[i], close[i]) - Math.min(start[i], close[i])) / (max - min) * mainView.h; 110 | x = toInt(x); 111 | y = toInt(y); 112 | w = toInt(w); 113 | h = toInt(h); 114 | ctx.fillRect(x, y, w, h < this.dpr ? this.dpr : h); 115 | let x1 = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 116 | let y1 = (max - hi[i]) / (max - min) * mainView.h + mainView.y; 117 | let x2 = x1; 118 | let y2 = (max - lo[i]) / (max - min) * mainView.h + mainView.y; 119 | x1 = toInt(x1); 120 | y1 = toInt(y1); 121 | x2 = toInt(x2); 122 | y2 = toInt(y2); 123 | ctx.beginPath(); 124 | ctx.moveTo(x1, y1); 125 | ctx.lineTo(x2, y2); 126 | ctx.stroke(); 127 | } 128 | 129 | // 画最高点,最低点 130 | ctx.fillStyle = this.colors.textColor; 131 | ctx.textBaseline = 'middle'; 132 | let index = (maxPriceIndex - startIndex); 133 | let index1 = (minPriceIndex - startIndex); 134 | let maxX = mainView.w / verticalRectNumber * 0.5 + (index + 0.1) * mainView.w / verticalRectNumber + mainView.x; 135 | let maxY = (max - maxPrice) / (max - min) * mainView.h + mainView.y; 136 | let minX = mainView.w / verticalRectNumber * 0.5 + (index1 + 0.1) * mainView.w / verticalRectNumber + mainView.x; 137 | let minY = (max - minPrice) / (max - min) * mainView.h + mainView.y; 138 | maxX = toInt(maxX); 139 | maxY = toInt(maxY); 140 | minX = toInt(minX); 141 | minY = toInt(minY); 142 | if (index < verticalRectNumber * 0.5) { 143 | ctx.textAlign = 'left'; 144 | ctx.fillText(' ← ' + this.string(maxPrice), maxX, maxY); 145 | } else { 146 | ctx.textAlign = 'right'; 147 | ctx.fillText(this.string(maxPrice) + ' → ', maxX, maxY); 148 | } 149 | if (index1 < verticalRectNumber * 0.5) { 150 | ctx.textAlign = 'left'; 151 | ctx.fillText(' ← ' + this.string(minPrice), minX, minY); 152 | } else { 153 | ctx.textAlign = 'right'; 154 | ctx.fillText(this.string(minPrice) + ' → ', minX, minY); 155 | } 156 | } else if (this.option.type === 'line') { 157 | ctx.beginPath(); 158 | ctx.strokeStyle = this.colors.lightColor; 159 | ctx.lineWidth = 2 * this.dpr; 160 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 161 | if (i >= times.length) { 162 | break; 163 | } 164 | let x = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 165 | let y = (max - this.state.close[i]) / (max - min) * mainView.h + mainView.y; 166 | x = toInt(x); 167 | y = toInt(y); 168 | if (j == 0) { 169 | ctx.moveTo(x, y); 170 | } 171 | ctx.lineTo(x, y); 172 | } 173 | ctx.stroke(); 174 | } 175 | ctx.lineWidth = this.dpr; 176 | 177 | if (this.option.mainCsi === 'ma') { 178 | // ma30 179 | ctx.beginPath(); 180 | ctx.strokeStyle = this.colors.ma30Color; 181 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 182 | if (i >= times.length) { 183 | break; 184 | } 185 | let x = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 186 | let y = (max - this.state.ma30[i]) / (max - min) * mainView.h + mainView.y; 187 | x = toInt(x); 188 | y = toInt(y); 189 | if (j == 0) { 190 | ctx.moveTo(x, y); 191 | } 192 | ctx.lineTo(x, y); 193 | } 194 | ctx.stroke(); 195 | 196 | // ma7 197 | ctx.beginPath(); 198 | ctx.strokeStyle = this.colors.ma7Color; 199 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 200 | if (i >= this.state.times.length) { 201 | break; 202 | } 203 | let x = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 204 | let y = (max - this.state.ma7[i]) / (max - min) * mainView.h + mainView.y; 205 | x = toInt(x); 206 | y = toInt(y); 207 | if (j == 0) { 208 | ctx.moveTo(x, y); 209 | } 210 | ctx.lineTo(x, y); 211 | } 212 | ctx.stroke(); 213 | } else if (this.option.mainCsi === 'ema') { 214 | // ema30 215 | ctx.beginPath(); 216 | ctx.strokeStyle = this.colors.ma30Color; 217 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 218 | if (i >= times.length) { 219 | break; 220 | } 221 | let x = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 222 | let y = (max - this.state.ema30[i]) / (max - min) * mainView.h + mainView.y; 223 | x = toInt(x); 224 | y = toInt(y); 225 | if (j == 0) { 226 | ctx.moveTo(x, y); 227 | } 228 | ctx.lineTo(x, y); 229 | } 230 | ctx.stroke(); 231 | 232 | // ema7 233 | ctx.beginPath(); 234 | ctx.strokeStyle = this.colors.ma7Color; 235 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 236 | if (i >= times.length) { 237 | break; 238 | } 239 | let x = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 240 | let y = (max - this.state.ema7[i]) / (max - min) * mainView.h + mainView.y; 241 | x = toInt(x); 242 | y = toInt(y); 243 | if (j == 0) { 244 | ctx.moveTo(x, y); 245 | } 246 | ctx.lineTo(x, y); 247 | } 248 | ctx.stroke(); 249 | } else if (this.option.mainCsi === 'boll') { 250 | // UP 251 | ctx.beginPath(); 252 | ctx.strokeStyle = this.colors.ma30Color; 253 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 254 | if (i >= times.length) { 255 | break; 256 | } 257 | let x = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 258 | let y = (max - this.state.up[i]) / (max - min) * mainView.h + mainView.y; 259 | x = toInt(x); 260 | y = toInt(y); 261 | if (j == 0) { 262 | ctx.moveTo(x, y); 263 | } 264 | ctx.lineTo(x, y); 265 | } 266 | ctx.stroke(); 267 | 268 | // MB 269 | ctx.beginPath(); 270 | ctx.strokeStyle = this.colors.ma7Color; 271 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 272 | if (i >= times.length) { 273 | break; 274 | } 275 | let x = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 276 | let y = (max - this.state.mb[i]) / (max - min) * mainView.h + mainView.y; 277 | x = toInt(x); 278 | y = toInt(y); 279 | if (j == 0) { 280 | ctx.moveTo(x, y); 281 | } 282 | ctx.lineTo(x, y); 283 | } 284 | ctx.stroke(); 285 | 286 | // DN 287 | ctx.beginPath(); 288 | ctx.strokeStyle = this.colors.macdColor; 289 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 290 | if (i >= times.length) { 291 | break; 292 | } 293 | let x = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 294 | let y = (max - this.state.dn[i]) / (max - min) * mainView.h + mainView.y; 295 | x = toInt(x); 296 | y = toInt(y); 297 | if (j == 0) { 298 | ctx.moveTo(x, y); 299 | } 300 | ctx.lineTo(x, y); 301 | } 302 | ctx.stroke(); 303 | } else if (this.option.mainCsi === 'sar') { 304 | ctx.strokeStyle = this.colors.macdColor; 305 | for (let i = startIndex, j = 0; j < verticalRectNumber; i++, j++) { 306 | if (i >= times.length) { 307 | break; 308 | } 309 | let x = j * mainView.w / verticalRectNumber + 0.5 * mainView.w / verticalRectNumber + mainView.x; 310 | let y = (max - this.state.sar[i]) / (max - min) * mainView.h + mainView.y; 311 | x = toInt(x); 312 | y = toInt(y); 313 | ctx.beginPath(); 314 | ctx.arc(x, y, mainView.w / verticalRectNumber / 6, 0, Math.PI * 2); 315 | ctx.stroke(); 316 | } 317 | } 318 | 319 | 320 | // 当前价格 321 | // ctx.fillStyle = this.colors.background; 322 | // ctx.fillRect(mainYaxisView.x + this.dpr, (max - close[close.length - 1]) / (max - min) * mainView.h + mainView.y - 10 * this.dpr, mainYaxisView.w - 2 * this.dpr, 20 * this.dpr); 323 | // ctx.strokeStyle = this.colors.textFrameColor; 324 | // ctx.strokeRect(mainYaxisView.x + this.dpr, (max - close[close.length - 1]) / (max - min) * mainView.h + mainView.y - 10 * this.dpr, mainYaxisView.w - 2 * this.dpr, 20 * this.dpr); 325 | // ctx.textAlign = 'center'; 326 | // ctx.fillStyle = this.colors.currentTextColor; 327 | // ctx.fillText(this.string(close[close.length - 1]), mainYaxisView.x + mainYaxisView.w * 0.5, (max - close[close.length - 1]) / (max - min) * mainView.h + mainView.y); 328 | } 329 | -------------------------------------------------------------------------------- /src2/drawTimeline.js: -------------------------------------------------------------------------------- 1 | function toInt(num) { 2 | return ~~(0.5 + num); 3 | } 4 | const getPeriod = n => { 5 | return (...arr) => { 6 | if (Math.floor(n) >= arr.length) { 7 | return arr[arr.length - 1]; 8 | } else { 9 | return arr[Math.floor(n)]; 10 | } 11 | }; 12 | }; 13 | export default function drawTimeline() { 14 | const ctx = this.ctx; 15 | const times = this.state.times; 16 | const timeStr = this.state.timeStr; 17 | const mainView = this.mainView; 18 | const timeView = this.timeView; 19 | const [startIndex, endIndex] = this.state.range; 20 | const verticalRectNumber = endIndex - startIndex; 21 | const period = this.option.period; 22 | // 时间轴 23 | ctx.textAlign = 'center'; 24 | ctx.textBaseline = 'middle'; 25 | let p; 26 | const n = verticalRectNumber / this.maxVerticalRectNumber * 5; 27 | if (period === 60) { 28 | p = getPeriod(n)(600, 1200, 1800, 2400, 3000); 29 | } else if (period === 60 * 3) { 30 | p = getPeriod(n)(600 * 3, 1200 * 3, 1800 * 3, 2400 * 3, 3000 * 3); 31 | } else if (period === 60 * 5) { 32 | p = getPeriod(n)(600 * 5, 1200 * 5, 1800 * 5, 2400 * 5, 3000 * 5); 33 | } else if (period === 60 * 15) { 34 | p = getPeriod(n)(600 * 15, 1200 * 15, 1800 * 15, 2400 * 15, 3000 * 15, 3600 * 15); 35 | } else if (period === 60 * 30) { 36 | p = getPeriod(n)(600 * 30, 1200 * 30, 1800 * 30, 2400 * 30, 3000 * 30, 3600 * 30); 37 | } else if (period === 60 * 60) { 38 | p = getPeriod(n)(600 * 60, 1200 * 60, 1800 * 60, 2400 * 60, 3000 * 60, 3600 * 60); 39 | } else if (period === 60 * 60 * 12) { 40 | p = getPeriod(n)(600 * 60 * 12, 1200 * 60 * 12, 1800 * 60 * 12, 2400 * 60 * 12, 3000 * 60 * 12, 3600 * 60 * 12); 41 | } else if (period === 60 * 60 * 24) { 42 | p = getPeriod(n)(600 * 60 * 24, 1200 * 60 * 24, 1800 * 60 * 24, 2400 * 60 * 24, 3000 * 60 * 24, 3600 * 60 * 24); 43 | } else if (period === 60 * 60 * 24 * 7) { 44 | p = getPeriod(n)(600 * 60 * 24 * 7, 1200 * 60 * 24 * 7, 1800 * 60 * 24 * 7, 2400 * 60 * 24 * 7, 3000 * 60 * 24 * 7, 3600 * 60 * 24 * 7); 45 | } else if (period === 60 * 60 * 24 * 30) { 46 | p = false; 47 | } 48 | const timeFilterParams = []; 49 | for (let i = startIndex; i < endIndex; i++) { 50 | if (i >= times.length) { 51 | break; 52 | } 53 | if (times[i] % p === 0) { 54 | let x = (i - startIndex) / verticalRectNumber * mainView.w + mainView.x; 55 | let y = timeView.y + timeView.h * 0.5; 56 | x = toInt(x); 57 | y = toInt(y); 58 | timeFilterParams.push({ x, y, time: times[i] }); 59 | // ctx.beginPath(); 60 | // ctx.moveTo(x, this.height - 2); 61 | // ctx.lineTo(x, this.height - 8); 62 | // ctx.stroke(); 63 | } 64 | } 65 | this.option.timeFilter(ctx, timeFilterParams); 66 | } 67 | -------------------------------------------------------------------------------- /src2/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import 'whatwg-fetch'; 3 | import { KLine, Depth, Depth2 } from './KLine'; 4 | var bodyWidth = document.body.clientWidth; 5 | var bodyHeight = document.body.clientHeight; 6 | 7 | var app = document.getElementById('app'); 8 | app.style.width = bodyWidth + 'px'; 9 | app.style.height = bodyHeight + 'px'; 10 | app.style.position = 'relative'; 11 | 12 | var canvas = document.createElement('canvas'); 13 | canvas.style.width = bodyWidth + 'px'; 14 | canvas.style.height = bodyHeight * 0.6 + 'px'; 15 | canvas.style.position = 'absolute'; 16 | canvas.width = bodyWidth * 2; 17 | canvas.height = bodyHeight * 0.6 * 2; 18 | var overCanvas = document.createElement('canvas'); 19 | overCanvas.style.width = bodyWidth + 'px'; 20 | overCanvas.style.height = bodyHeight * 0.6 + 'px'; 21 | overCanvas.style.position = 'absolute'; 22 | overCanvas.style.top = 0; 23 | overCanvas.style.left = 0; 24 | overCanvas.width = bodyWidth * 2; 25 | overCanvas.height = bodyHeight * 0.6 * 2; 26 | 27 | app.appendChild(canvas); 28 | app.appendChild(overCanvas); 29 | 30 | const period = 60 * 60; 31 | fetch(`http://120.26.102.105:8080/marketCenter/market/v0/kline?symbol=huobi_btc_cny&type=${period}`).then(res => { 32 | return res.json(); 33 | }).then(json => { 34 | let chart = new KLine(canvas, overCanvas, { 35 | data: json, 36 | period, 37 | theme: 'dark', 38 | priceDecimal: 2, 39 | timeFilter: function(ctx, d) { 40 | if (d.length < 1) { 41 | return; 42 | } 43 | let cha = (d[d.length - 1].time - d[0].time) / (d.length - 1); 44 | let data; 45 | if (cha < 3600) { 46 | data = d.map(el => ({ time: new Date(el.time * 1000).toString('d日 H:m'), x: el.x, y: el.y })); 47 | } else if (cha < 3600 * 24) { 48 | data = d.map(el => ({ time: new Date(el.time * 1000).toString('d日 H'), x: el.x, y: el.y })); 49 | } else if (cha < 3600 * 24 * 31) { 50 | data = d.map(el => ({ time: new Date(el.time * 1000).toString('yyyy/M/d'), x: el.x, y: el.y })); 51 | } else { 52 | data = d.map(el => ({ time: new Date(el.time * 1000).toString('yyyy/M'), x: el.x, y: el.y })); 53 | } 54 | data.forEach(el => { 55 | ctx.fillText(el.time, el.x, el.y); 56 | }); 57 | }, 58 | type: 'line', 59 | mainCsi: '', 60 | aidCsi: 'kdj', 61 | overTimeFilter: function(d) { 62 | return new Date(d * 1000).toString('yyyy/MM/dd HH:mm'); 63 | } 64 | }); 65 | // chart.beginDrawLine('arrow'); 66 | console.log(chart); 67 | window.addEventListener('resize', function(e) { 68 | var bodyWidth = document.body.clientWidth; 69 | var bodyHeight = document.body.clientHeight; 70 | app.style.width = bodyWidth + 'px'; 71 | app.style.height = bodyHeight * 0.6 + 'px'; 72 | canvas.style.width = bodyWidth + 'px'; 73 | canvas.style.height = bodyHeight * 0.6 + 'px'; 74 | canvas.width = bodyWidth * 2; 75 | canvas.height = bodyHeight * 0.6 * 2; 76 | overCanvas.style.width = bodyWidth + 'px'; 77 | overCanvas.style.height = bodyHeight * 0.6 + 'px'; 78 | overCanvas.width = bodyWidth * 2; 79 | overCanvas.height = bodyHeight * 0.6 * 2; 80 | chart.setOption({}); 81 | }); 82 | }); 83 | 84 | 85 | fetch('http://120.26.102.105:8080/marketCenter/market/v0/depth?symbol=huobi_btc').then(res => { 86 | return res.json(); 87 | }).then(json => { 88 | var depthCanvas = document.createElement('canvas'); 89 | depthCanvas.style.width = bodyWidth + 'px'; 90 | depthCanvas.style.height = bodyHeight * 0.4 + 'px'; 91 | depthCanvas.style.position = 'absolute'; 92 | depthCanvas.width = bodyWidth * 2; 93 | depthCanvas.height = bodyHeight * 0.4 * 2; 94 | depthCanvas.style.top = bodyHeight * 0.6 + 'px'; 95 | depthCanvas.style.left = 0; 96 | app.appendChild(depthCanvas); 97 | 98 | var depth = new Depth2(depthCanvas, json, { theme: 'dark' }); 99 | }); 100 | -------------------------------------------------------------------------------- /src2/operation.js: -------------------------------------------------------------------------------- 1 | export default function operation(canvas, overCanvas) { 2 | const overCtx = this.overCtx; 3 | const getMainView = () => this.mainView; 4 | const getAidView = () => this.aidView; 5 | 6 | let isDown = false; 7 | let lastIndex = -1; 8 | let lastPrice = -1; 9 | let lastTouchDistance = 0; 10 | let moveLine = null; 11 | 12 | const move = e => { 13 | const mainView = getMainView(); 14 | const aidView = getAidView(); 15 | const pos = this.getMousePos(e); 16 | this.mousePos = pos; 17 | let [startIndex, endIndex] = this.state.range; 18 | const verticalRectNumber = endIndex - startIndex; 19 | const currentIndex = Math.floor((pos.x - aidView.x) / aidView.w * verticalRectNumber); 20 | const { max, min } = this.computAxis(); 21 | const price = max - (max - min) * (pos.y - mainView.y) / mainView.h; 22 | if (isDown) { 23 | if (moveLine && moveLine.moving) { 24 | if (pos.x > mainView.x && pos.x < (mainView.x + mainView.w) && pos.y > mainView.y && pos.y < (mainView.y + mainView.h)) { 25 | moveLine.move(currentIndex - lastIndex, price - lastPrice); 26 | } 27 | } else { 28 | this.moveRange(currentIndex - lastIndex); 29 | } 30 | } 31 | if (this.isInLineView(pos)) { 32 | this.pos = pos; 33 | if (this.lineCache && pos.x > mainView.x && pos.x < (mainView.x + mainView.w) && pos.y > mainView.y && pos.y < (mainView.y + mainView.h)) { 34 | this.lineCache.setPosition(currentIndex + startIndex, price); 35 | } 36 | this.forceUpdate(false, true); 37 | } 38 | lastIndex = currentIndex; 39 | lastPrice = price; 40 | }; 41 | 42 | const scale = n => { 43 | if (n > 20) { 44 | n = 20; 45 | } 46 | if (n < -20) { 47 | n = -20; 48 | } 49 | this.scaleRange(n); 50 | }; 51 | 52 | if (this.device === 'pc') { 53 | const mousedown = e => { 54 | const aidView = getAidView(); 55 | const pos = this.getMousePos(e); 56 | if (e.button === 0) { 57 | isDown = true; 58 | this.lines.forEach(line => { 59 | if (line.isInPath(pos)) { 60 | moveLine = line; 61 | moveLine.moving = true; 62 | return; 63 | } 64 | }); 65 | const verticalRectNumber = this.state.range[1] - this.state.range[0]; 66 | const currentIndex = Math.floor((pos.x - aidView.x) / aidView.w * verticalRectNumber); 67 | lastIndex = currentIndex; 68 | } else if (e.button === 2) { 69 | overCanvas.oncontextmenu = () => false; 70 | let index = null; 71 | this.lines.forEach((line, i) => { 72 | if (line.isInPath(pos)) { 73 | index = i; 74 | return; 75 | } 76 | }); 77 | if (index !== null) { 78 | this.clearLine(index); 79 | } 80 | } 81 | this.forceUpdate(false, true); 82 | }; 83 | const mouseup = () => { 84 | isDown = false; 85 | if (moveLine) { 86 | moveLine.moving = false; 87 | moveLine = null; 88 | } 89 | this.forceUpdate(false, true); 90 | }; 91 | const mouseout = () => { 92 | isDown = false; 93 | if (moveLine) { 94 | moveLine.moving = false; 95 | moveLine = null; 96 | } 97 | this.forceUpdate(false, true); 98 | }; 99 | overCanvas.addEventListener('mousedown', mousedown); 100 | overCanvas.addEventListener('mouseup', mouseup); 101 | overCanvas.addEventListener('mouseout', mouseout); 102 | overCanvas.addEventListener('mousemove', move); 103 | overCanvas.addEventListener('wheel', function(e) { 104 | e.preventDefault(); 105 | let n = Number(e.deltaY.toFixed(0)); 106 | scale(n); 107 | }); 108 | overCanvas.addEventListener('click', e => { 109 | e.preventDefault(); 110 | if (!this.lineCache) { 111 | return; 112 | } 113 | const pos = this.getMousePos(e); 114 | const complete = this.lineCache.next(); 115 | if (complete) { 116 | this.lines.unshift(this.lineCache); 117 | this.lineCache = null; 118 | } 119 | this.forceUpdate(false, true); 120 | }); 121 | } else { 122 | const touchstart = e => { 123 | isDown = true; 124 | const mainView = getMainView(); 125 | if (e.targetTouches.length == 2) { 126 | const touch1 = this.getMousePos(e.targetTouches[0]); 127 | const touch2 = this.getMousePos(e.targetTouches[1]); 128 | lastTouchDistance = Math.sqrt(Math.pow(touch1.x - touch2.x, 2) + Math.pow(touch1.y - touch2.y, 2)); 129 | } else if (e.targetTouches.length === 1) { 130 | const pos = this.getMousePos(e.targetTouches[0]); 131 | let [startIndex, endIndex] = this.state.range; 132 | const verticalRectNumber = endIndex - startIndex; 133 | const currentIndex = Math.floor((pos.x - mainView.x) / mainView.w * verticalRectNumber); 134 | lastIndex = currentIndex; 135 | this.pos = pos; 136 | this.forceUpdate(false, true); 137 | } 138 | }; 139 | const touchend = () => { 140 | isDown = false; 141 | this.forceUpdate(false, true); 142 | }; 143 | const touchcancel = () => { 144 | isDown = false; 145 | overCtx.clearRect(0, 0, this.width, this.height); 146 | this.forceUpdate(false, true); 147 | }; 148 | const touchmove = e => { 149 | e.preventDefault(); 150 | if (e.targetTouches.length === 2) { 151 | const touch1 = this.getMousePos(e.targetTouches[0]); 152 | const touch2 = this.getMousePos(e.targetTouches[1]); 153 | const currentDistance = Math.sqrt(Math.pow(touch1.x - touch2.x, 2) + Math.pow(touch1.y - touch2.y, 2)); 154 | let [startIndex, endIndex] = this.state.range; 155 | const verticalRectNumber = endIndex - startIndex; 156 | let n = (verticalRectNumber - currentDistance / lastTouchDistance * verticalRectNumber); 157 | lastTouchDistance = currentDistance; 158 | if (n > 0) { 159 | n = Math.ceil(n); 160 | } else { 161 | n = Math.floor(n); 162 | } 163 | this.pos = touch1; 164 | scale(n); 165 | } else { 166 | move(e.targetTouches[0]); 167 | } 168 | this.forceUpdate(false, true); 169 | }; 170 | overCanvas.addEventListener('touchstart', touchstart); 171 | overCanvas.addEventListener('touchend', touchend); 172 | overCanvas.addEventListener('touchcancel', touchcancel); 173 | overCanvas.addEventListener('touchmove', touchmove); 174 | } 175 | } 176 | 177 | export function drawHairLine() { 178 | const pos = this.pos; 179 | if (!pos) { 180 | return; 181 | } 182 | const overCtx = this.overCtx; 183 | const { mainView, mainYaxisView, aidView, aidYaxisView, timeView } = this; 184 | let [startIndex, endIndex] = this.state.range; 185 | const verticalRectNumber = endIndex - startIndex; 186 | 187 | const currentIndex = Math.floor((pos.x - aidView.x) / aidView.w * verticalRectNumber); 188 | const x = currentIndex * aidView.w / verticalRectNumber + aidView.w / verticalRectNumber * 0.5 + mainView.x; 189 | const y = pos.y; 190 | 191 | // overCtx.clearRect(0, 0, this.width, this.height); 192 | if (currentIndex + startIndex >= this.state.times.length || currentIndex + startIndex < 0) { 193 | return; 194 | } 195 | 196 | overCtx.lineWidth = this.dpr; 197 | overCtx.strokeStyle = this.colors.hairLine; 198 | 199 | overCtx.beginPath(); 200 | overCtx.moveTo(x, this.height); 201 | overCtx.lineTo(x, 0); 202 | overCtx.stroke(); 203 | 204 | overCtx.beginPath(); 205 | overCtx.moveTo(0, y); 206 | overCtx.lineTo(this.width, y); 207 | overCtx.stroke(); 208 | 209 | // x轴坐标 210 | const currentTime = this.option.overTimeFilter(this.state.times[startIndex + currentIndex]); 211 | overCtx.textAlign = 'center'; 212 | overCtx.textBaseline = 'middle'; 213 | overCtx.fillStyle = this.colors.background; 214 | overCtx.fillRect(x - overCtx.measureText(currentTime).width * 0.5 - 10, timeView.y + this.dpr, overCtx.measureText(currentTime).width + 20, timeView.h - this.dpr * 2); 215 | overCtx.strokeStyle = this.colors.textFrameColor; 216 | overCtx.strokeRect(x - overCtx.measureText(currentTime).width * 0.5 - 10, timeView.y + this.dpr, overCtx.measureText(currentTime).width + 20, timeView.h - this.dpr * 2); 217 | overCtx.fillStyle = this.colors.currentTextColor; 218 | overCtx.fillText(currentTime, x, timeView.h * 0.5 + timeView.y); 219 | 220 | // 画y轴坐标 221 | const { max, min } = this.computAxis(); 222 | let view = mainYaxisView; 223 | let w = this.width - view.x; 224 | overCtx.textAlign = 'right'; 225 | overCtx.textBaseline = 'middle'; 226 | overCtx.fillStyle = this.colors.background; 227 | overCtx.fillRect(view.x + this.dpr, y - 10 * this.dpr, w - 2 * this.dpr, 20 * this.dpr); 228 | overCtx.strokeStyle = this.colors.textFrameColor; 229 | overCtx.strokeRect(view.x + this.dpr, y - 10 * this.dpr, w - 2 * this.dpr, 20 * this.dpr); 230 | overCtx.fillStyle = this.colors.textColor; 231 | 232 | overCtx.textAlign = 'center'; 233 | overCtx.fillStyle = this.colors.currentTextColor; 234 | if (this.isInLineView(pos) === mainView) { 235 | const yText = max - (max - min) * (y - view.y) / view.h; 236 | overCtx.fillText(yText.toFixed(this.option.priceDecimal), mainYaxisView.x + mainYaxisView.w * 0.5, y); 237 | } else { 238 | view = aidYaxisView; 239 | if (this.option.aidCsi === 'volume') { 240 | const yText = (1 - (y - view.y) / view.h) * (this.csiYaxisSector[0] - this.csiYaxisSector[1]); 241 | overCtx.fillText(this.setDP(yText), mainYaxisView.x + mainYaxisView.w * 0.5, y); 242 | } else if (this.option.aidCsi === 'macd' || this.option.aidCsi === 'kdj') { 243 | const yText = this.csiYaxisSector[1] * (y - view.y) / view.h + this.csiYaxisSector[0] * (1 - (y - view.y) / view.h); 244 | overCtx.fillText(this.setDP(yText), mainYaxisView.x + mainYaxisView.w * 0.5, y); 245 | } 246 | } 247 | 248 | const basicSelectOption = { 249 | time: this.state.times[currentIndex + startIndex], 250 | start: this.state.start[currentIndex + startIndex], 251 | hi: this.state.hi[currentIndex + startIndex], 252 | lo: this.state.lo[currentIndex + startIndex], 253 | close: this.state.close[currentIndex + startIndex], 254 | volume: this.state.volume[currentIndex + startIndex], 255 | }; 256 | let selectOption = { ...basicSelectOption }; 257 | if (this.option.mainCsi === 'ma') { 258 | selectOption = { 259 | ...selectOption, 260 | ma7: this.state.ma7[currentIndex + startIndex], 261 | ma30: this.state.ma30[currentIndex + startIndex], 262 | }; 263 | } else if (this.option.mainCsi === 'ema') { 264 | selectOption = { 265 | ...selectOption, 266 | ema7: this.state.ema7[currentIndex + startIndex], 267 | ema30: this.state.ema30[currentIndex + startIndex], 268 | }; 269 | } else if (this.option.mainCsi === 'boll') { 270 | selectOption = { 271 | ...selectOption, 272 | up: this.state.up[currentIndex + startIndex], 273 | mb: this.state.mb[currentIndex + startIndex], 274 | dn: this.state.dn[currentIndex + startIndex], 275 | }; 276 | } 277 | 278 | this.select(selectOption, 0); 279 | 280 | if (this.option.aidCsi === 'volume') { 281 | this.select({ 282 | volume: this.state.volume[currentIndex + startIndex], 283 | ma7: this.state.volumeMa7[currentIndex + startIndex], 284 | ma30: this.state.volumeMa30[currentIndex + startIndex], 285 | }, 1); 286 | } 287 | if (this.option.aidCsi === 'macd') { 288 | this.select({ 289 | dif: this.state.dif[currentIndex + startIndex], 290 | dea: this.state.dea[currentIndex + startIndex], 291 | macd: this.state.macd[currentIndex + startIndex], 292 | }, 1); 293 | } 294 | if (this.option.aidCsi === 'kdj') { 295 | this.select({ 296 | k: this.state.k[currentIndex + startIndex], 297 | d: this.state.d[currentIndex + startIndex], 298 | j: this.state.j[currentIndex + startIndex], 299 | }, 1); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src2/range.js: -------------------------------------------------------------------------------- 1 | export function moveRange(distance) { 2 | const [startIndex, endIndex] = this.state.range; 3 | const verticalRectNumber = endIndex - startIndex; 4 | let newStartIndex = startIndex - distance; 5 | let newEndIndex = endIndex - distance; 6 | if (newStartIndex >= this.state.times.length) { 7 | newStartIndex = this.state.times.length - 1; 8 | newEndIndex = newStartIndex + verticalRectNumber; 9 | } 10 | if (newStartIndex < 0) { 11 | newStartIndex = 0; 12 | newEndIndex = verticalRectNumber; 13 | } 14 | this.state = { ...this.state, range: [newStartIndex, newEndIndex] }; 15 | } 16 | 17 | export function scaleRange(n) { 18 | const [startIndex, endIndex] = this.state.range; 19 | const verticalRectNumber = endIndex - startIndex; 20 | let newStartIndex = startIndex - n; 21 | let newEndIndex = endIndex + n; 22 | if ((endIndex - startIndex) * 0.5 > this.state.times.length - startIndex) { 23 | newStartIndex += n; 24 | newEndIndex += n; 25 | } 26 | let newVerticalRectNumber = newEndIndex - newStartIndex; 27 | if (newVerticalRectNumber < this.minVerticalRectNumber) { 28 | newStartIndex = startIndex - (this.minVerticalRectNumber - verticalRectNumber) * 0.5; 29 | newEndIndex = endIndex + (this.minVerticalRectNumber - verticalRectNumber) * 0.5; 30 | } 31 | if (newVerticalRectNumber > this.maxVerticalRectNumber) { 32 | newStartIndex = startIndex - (this.maxVerticalRectNumber - verticalRectNumber) * 0.5; 33 | newEndIndex = endIndex + (this.maxVerticalRectNumber - verticalRectNumber) * 0.5; 34 | } 35 | newVerticalRectNumber = newEndIndex - newStartIndex; 36 | 37 | if (newStartIndex >= this.state.times.length) { 38 | newStartIndex = this.state.times.length - 1; 39 | newEndIndex = newStartIndex + newVerticalRectNumber; 40 | } 41 | if (newStartIndex < 0) { 42 | newStartIndex = 0; 43 | newEndIndex = newVerticalRectNumber; 44 | } 45 | this.state = { ...this.state, range: [newStartIndex, newEndIndex] }; 46 | } 47 | -------------------------------------------------------------------------------- /src2/select.js: -------------------------------------------------------------------------------- 1 | function transformKey(key) { 2 | if (key === 'time') { 3 | return '时间'; 4 | } else if (key === 'start') { 5 | return '开'; 6 | } else if (key === 'hi') { 7 | return '高'; 8 | } else if (key === 'lo') { 9 | return '低'; 10 | } else if (key === 'close') { 11 | return '收'; 12 | } else if (key === 'volume') { 13 | return '量'; 14 | } else if (key === 'macd') { 15 | return 'MACD'; 16 | } else if (key === 'ema7') { 17 | return 'EMA7'; 18 | } else if (key === 'ema30') { 19 | return 'EMA30'; 20 | } else if (key === 'ma7') { 21 | return 'MA7'; 22 | } else if (key === 'ma30') { 23 | return 'MA30'; 24 | } else if (key === 'dif') { 25 | return 'DIF'; 26 | } else if (key === 'dea') { 27 | return 'DEA'; 28 | } else if (key === 'up') { 29 | return 'UP'; 30 | } else if (key === 'mb') { 31 | return 'MB'; 32 | } else if (key === 'dn') { 33 | return 'DN'; 34 | } else if (key === 'k') { 35 | return 'K'; 36 | } else if (key === 'd') { 37 | return 'D'; 38 | } else if (key === 'j') { 39 | return 'J'; 40 | } else { 41 | return key; 42 | } 43 | } 44 | 45 | function setStyle(key, ctx) { 46 | key = key.toLowerCase(); 47 | if (key === 'ema7' || key === 'ma7' || key === 'dif' || key === 'mb' || key === 'k') { 48 | ctx.fillStyle = this.colors.ma7Color; 49 | } else if (key === 'ema30' || key === 'ma30' || key === 'dea' || key === 'up' || key === 'd') { 50 | ctx.fillStyle = this.colors.ma30Color; 51 | } else if (key === 'macd' || key === 'dn' || key === 'j') { 52 | ctx.fillStyle = this.colors.macdColor; 53 | } else { 54 | ctx.fillStyle = this.colors.textColor; 55 | } 56 | } 57 | 58 | export default function(data, flag) { 59 | let overCtx = this.overCtx; 60 | overCtx.textAlign = 'left'; 61 | overCtx.textBaseline = 'top'; 62 | if (flag === 0) { 63 | if (this.device === 'pc') { 64 | let x = 5; 65 | let y = 5; 66 | for (let i = 0; i < Object.keys(data).length; i++) { 67 | let key = Object.keys(data)[i]; 68 | let text; 69 | if (key === 'time') { 70 | text = '时间:' + this.option.overTimeFilter(data[key]); 71 | } else { 72 | text = transformKey(key) + ':' + this.string(data[key]); 73 | } 74 | if (overCtx.measureText(text).width + x + 40 > this.mainView.x + this.mainView.w) { 75 | x = 5; 76 | y += 40; 77 | } 78 | setStyle.call(this, key, overCtx); 79 | overCtx.fillText(text, x, y); 80 | x += overCtx.measureText(text).width + 40; 81 | } 82 | } else { 83 | let text = `${this.option.overTimeFilter(data.time)} 开${this.string(data.start)} 高${this.string(data.hi)} 低${this.string(data.lo)} 收${this.string(data.close)}`; 84 | overCtx.textAlign = 'center'; 85 | overCtx.textBaseline = 'middle'; 86 | overCtx.fillStyle = this.colors.mobileBar; 87 | overCtx.fillRect(0, 0, this.width, 32 * this.dpr); 88 | overCtx.fillStyle = this.colors.textColor; 89 | overCtx.fillText(text, this.width * 0.5, 16 * this.dpr); 90 | let x = 5; 91 | let y = 32 * this.dpr + 5; 92 | overCtx.textAlign = 'left'; 93 | overCtx.textBaseline = 'top'; 94 | for (let i = 0; i < Object.keys(data).length; i++) { 95 | let key = Object.keys(data)[i]; 96 | if (/(time|start|hi|lo|end)/g.test(key)) { 97 | continue; 98 | } 99 | text = transformKey(key) + ':' + this.string(data[key]); 100 | if (overCtx.measureText(text).width + x + 40 > this.mainView.x + this.mainView.w) { 101 | x = 5; 102 | y += 40; 103 | } 104 | setStyle.call(this, key, overCtx); 105 | overCtx.fillText(text, x, y); 106 | x += overCtx.measureText(text).width + 40; 107 | } 108 | } 109 | } else if (flag === 1) { 110 | let x = 5; 111 | let y = this.aidView.y; 112 | for (let i = 0; i < Object.keys(data).length; i++) { 113 | let key = Object.keys(data)[i]; 114 | let text = transformKey(key) + ':' + this.string(data[key]); 115 | if (overCtx.measureText(text).width + x + 40 > this.mainView.x + this.mainView.w) { 116 | x = 5; 117 | y += 40; 118 | } 119 | setStyle.call(this, key, overCtx); 120 | overCtx.fillText(text, x, y); 121 | x += overCtx.measureText(text).width + 40; 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src2/setData.js: -------------------------------------------------------------------------------- 1 | export default function setData() { 2 | let maxLength = -1; 3 | const data = this.option.data; 4 | let times = []; 5 | let start = []; 6 | let hi = []; 7 | let lo = []; 8 | let close = []; 9 | let volume = []; 10 | data.forEach(d => { 11 | times.push(d[0]); 12 | start.push(d[1]); 13 | hi.push(d[2]); 14 | lo.push(d[3]); 15 | close.push(d[4]); 16 | volume.push(d[5]); 17 | maxLength = Math.max( 18 | maxLength, 19 | d[1].toFixed(this.option.priceDecimal).length, 20 | d[2].toFixed(this.option.priceDecimal).length, 21 | d[3].toFixed(this.option.priceDecimal).length, 22 | d[4].toFixed(this.option.priceDecimal).length, 23 | d[5].toFixed(this.option.priceDecimal).length 24 | ); 25 | }); 26 | this.state = { 27 | times, 28 | start, 29 | hi, 30 | lo, 31 | close, 32 | volume, 33 | ma30: close.map((el, i) => { 34 | if (i < 29) { 35 | return el; 36 | } else { 37 | let sum = 0; 38 | for (let index = i; index > i - 30; index--) { 39 | sum += close[index]; 40 | } 41 | return this.setDP(sum / 30, this.option.priceDecimal + 2); 42 | } 43 | }), 44 | ma20: close.map((el, i) => { 45 | if (i < 19) { 46 | return el; 47 | } else { 48 | let sum = 0; 49 | for (let index = i; index > i - 20; index--) { 50 | sum += close[index]; 51 | } 52 | return this.setDP(sum / 20, this.option.priceDecimal + 2); 53 | } 54 | }), 55 | ma7: close.map((el, i) => { 56 | if (i < 6) { 57 | return el; 58 | } else { 59 | let sum = 0; 60 | for (let index = i; index > i - 7; index--) { 61 | sum += close[index]; 62 | } 63 | return this.setDP(sum / 7, this.option.priceDecimal + 2); 64 | } 65 | }), 66 | volumeMa7: volume.map((el, i) => { 67 | if (i < 6) { 68 | return el; 69 | } else { 70 | let sum = 0; 71 | for (let index = i; index > i - 7; index--) { 72 | sum += volume[index]; 73 | } 74 | return this.setDP(sum / 7); 75 | } 76 | }), 77 | volumeMa30: volume.map((el, i) => { 78 | if (i < 29) { 79 | return el; 80 | } else { 81 | let sum = 0; 82 | for (let index = i; index > i - 30; index--) { 83 | sum += volume[index]; 84 | } 85 | return this.setDP(sum / 30); 86 | } 87 | }), 88 | isDown: false, 89 | range: data.length > 70 ? [data.length - 70, data.length + 18] : [0, 88], 90 | }; 91 | this.state.ema30 = []; 92 | this.state.close.forEach((el, i) => { 93 | if (i === 0) { 94 | this.state.ema30[i] = el; 95 | } else { 96 | let val = 2 / 31 * (this.state.close[i] - this.state.ema30[i - 1]) + this.state.ema30[i - 1]; 97 | this.state.ema30[i] = this.setDP(val, this.option.priceDecimal + 2); 98 | } 99 | }); 100 | this.state.ema7 = []; 101 | this.state.close.forEach((el, i) => { 102 | if (i === 0) { 103 | this.state.ema7[i] = el; 104 | } else { 105 | let val = 2 / 8 * (this.state.close[i] - this.state.ema7[i - 1]) + this.state.ema7[i - 1]; 106 | this.state.ema7[i] = this.setDP(val, this.option.priceDecimal + 2); 107 | } 108 | }); 109 | this.state.ema15 = []; 110 | this.state.close.forEach((el, i) => { 111 | if (i === 0) { 112 | this.state.ema15[i] = el; 113 | } else { 114 | let val = 2 / 16 * (this.state.close[i] - this.state.ema15[i - 1]) + this.state.ema15[i - 1]; 115 | this.state.ema15[i] = this.setDP(val, this.option.priceDecimal + 2); 116 | } 117 | }); 118 | this.state.ema26 = []; 119 | this.state.close.forEach((el, i) => { 120 | if (i === 0) { 121 | this.state.ema26[i] = el; 122 | } else { 123 | let val = 2 / 27 * (this.state.close[i] - this.state.ema26[i - 1]) + this.state.ema26[i - 1]; 124 | this.state.ema26[i] = this.setDP(val, this.option.priceDecimal + 2); 125 | } 126 | }); 127 | this.state.ema12 = []; 128 | this.state.close.forEach((el, i) => { 129 | if (i === 0) { 130 | this.state.ema12[i] = el; 131 | } else { 132 | let val = 2 / 13 * (this.state.close[i] - this.state.ema12[i - 1]) + this.state.ema12[i - 1]; 133 | this.state.ema12[i] = this.setDP(val, this.option.priceDecimal + 2); 134 | } 135 | }); 136 | this.state.dif = this.state.ema12.map((el, i) => { 137 | let val = el - this.state.ema26[i]; 138 | return this.setDP(val); 139 | }); 140 | this.state.dea = []; 141 | this.state.dif.forEach((el, i) => { 142 | if (i === 0) { 143 | this.state.dea[i] = el; 144 | } else { 145 | let val = this.state.dea[i - 1] * 0.8 + el * 0.2; 146 | this.state.dea[i] = this.setDP(val); 147 | } 148 | }); 149 | this.state.macd = this.state.dif.map((el, i) => { 150 | let val = (el - this.state.dea[i]) * 2; 151 | const macd = this.setDP(val); 152 | maxLength = Math.max(maxLength, macd.toString().length); 153 | return macd; 154 | }); 155 | 156 | // 计算BOLL 157 | this.state.up = []; 158 | this.state.mb = []; 159 | this.state.dn = []; 160 | this.state.ma20.forEach((el, i) => { 161 | if (i === 0) { 162 | this.state.mb.push(this.state.ma20[i]); 163 | this.state.up.push(this.state.ma20[i]); 164 | this.state.dn.push(this.state.ma20[i]); 165 | return; 166 | } 167 | let sum = 0; 168 | for (let index = i < 20 ? 0 : i - 20; index < i; index++) { 169 | sum += (this.state.close[index] - this.state.ma20[index]) ** 2; 170 | } 171 | let md = Math.sqrt(sum / (i < 20 ? i : 20)); 172 | this.state.mb.push(this.setDP(this.state.ma20[i - 1], this.option.priceDecimal + 2)); 173 | this.state.up.push(this.setDP(this.state.ma20[i - 1] + 2 * md, this.option.priceDecimal + 2)); 174 | this.state.dn.push(this.setDP(this.state.ma20[i - 1] - 2 * md, this.option.priceDecimal + 2)); 175 | }); 176 | 177 | // 计算kdj 178 | this.state.k = []; 179 | this.state.d = []; 180 | this.state.j = []; 181 | this.state.close.forEach((el, i) => { 182 | let h = this.state.hi[i - 8 < 0 ? 0 : i - 8]; 183 | let l = this.state.lo[i - 8 < 0 ? 0 : i - 8]; 184 | let defaultIndex = i - 8 < 0 ? 0 : i - 8; 185 | for (let index = defaultIndex; index <= i; index++) { 186 | l = Math.min(this.state.lo[index], l); 187 | h = Math.max(this.state.hi[index], h); 188 | } 189 | let rsv; 190 | if (h === l) { 191 | rsv = 100; 192 | } else { 193 | rsv = (el - l) / (h - l) * 100; 194 | } 195 | if (i === 0) { 196 | this.state.k.push(this.setDP(100 / 3 + rsv / 3)); 197 | this.state.d.push(this.setDP(100 / 3 + this.state.k[i] / 3)); 198 | this.state.j.push(this.setDP(3 * this.state.k[i] - 2 * this.state.d[i])); 199 | return; 200 | } 201 | this.state.k.push(this.setDP(2 / 3 * this.state.k[i - 1] + rsv / 3)); 202 | this.state.d.push(this.setDP(2 / 3 * this.state.d[i - 1] + this.state.k[i] / 3)); 203 | this.state.j.push(this.setDP(3 * this.state.k[i] - 2 * this.state.d[i])); 204 | }); 205 | 206 | // 计算sar 207 | this.state.sar = []; 208 | let af = 0.02; 209 | for (let i = 0; i < times.length; i++) { 210 | if (i === 0) { 211 | this.state.sar.push(this.state.lo[i]); 212 | continue; 213 | } 214 | if (i === 1) { 215 | this.state.sar.push(this.state.hi[i]); 216 | continue; 217 | } 218 | let ep; 219 | if (this.state.close[i] > this.state.close[i - 1]) { 220 | ep = Math.max(this.state.hi[i - 1], this.state.hi[i - 2]); 221 | } else { 222 | ep = Math.min(this.state.lo[i - 1], this.state.lo[i - 2]); 223 | } 224 | if (this.state.close[i] > this.state.close[i - 1] && this.state.close[i - 1] > this.state.close[i - 2]) { 225 | if (Math.max(this.state.hi[i], this.state.hi[i - 1]) > Math.max(this.state.hi[i - 1], this.state.hi[i - 2])) { 226 | af = af + 0.02 > 0.2 ? 0.2 : af + 0.02; 227 | } 228 | } else if (this.state.close[i] <= this.state.close[i - 1] && this.state.close[i - 1] <= this.state.close[i - 2]) { 229 | if (Math.min(this.state.lo[i], this.state.lo[i - 1]) < Math.min(this.state.lo[i - 1], this.state.lo[i - 2])) { 230 | af = af + 0.02 > 0.2 ? 0.2 : af + 0.02; 231 | } 232 | } else { 233 | af = 0.02; 234 | } 235 | let preSar = this.state.sar[i - 1]; 236 | let sar = preSar + af * (ep - preSar); 237 | if (this.state.close[i] > this.state.close[i - 1]) { 238 | if (sar > this.state.lo[i] || sar > this.state.lo[i - 1] || sar > this.state.lo[i - 2]) { 239 | sar = Math.min(this.state.lo[i], this.state.lo[i - 1], this.state.lo[i - 2]); 240 | } 241 | } else { 242 | if (sar < this.state.hi[i] || sar < this.state.hi[i - 1] || sar < this.state.hi[i - 2]) { 243 | sar = Math.max(this.state.hi[i], this.state.hi[i - 1], this.state.hi[i - 2]); 244 | } 245 | } 246 | this.state.sar.push(sar); 247 | } 248 | maxLength = maxLength > 20 ? 20 : maxLength; 249 | 250 | return Math.ceil(this.ctx.measureText(10 ** maxLength).width + 10 * this.dpr); 251 | } 252 | -------------------------------------------------------------------------------- /src2/setOption.js: -------------------------------------------------------------------------------- 1 | Date.prototype.format = function(fmt) { 2 | if (this == 'Invalid Date') { 3 | return ''; 4 | } 5 | var o = { 6 | 'M+': this.getMonth() + 1, 7 | 'D+': this.getDate(), 8 | 'h+': this.getHours(), 9 | 'm+': this.getMinutes(), 10 | 's+': this.getSeconds(), 11 | 'q+': Math.floor((this.getMonth() + 3) / 3), 12 | 'S': this.getMilliseconds(), 13 | }; 14 | if (/(Y+)/.test(fmt)) { 15 | fmt = fmt.replace(RegExp.$1, (this.getFullYear() + '') 16 | .substr(4 - RegExp.$1.length)); 17 | } 18 | for (var k in o) { 19 | if (new RegExp('(' + k + ')').test(fmt)) { 20 | fmt = fmt.replace(RegExp.$1, 21 | (RegExp.$1.length == 1) ? 22 | (o[k]) : (('00' + o[k]).substr(('' + o[k]).length))); 23 | } 24 | } 25 | return fmt; 26 | }; 27 | export default function setOption(option = {}) { 28 | this.width = this.canvas.width; 29 | this.height = this.canvas.height; 30 | let data = option.data; 31 | if (this.option) { 32 | const lastPeriod = this.option.period; 33 | this.option = { 34 | theme: option.theme || this.option.theme, 35 | fontSize: option.fontSize || this.option.fontSize, 36 | type: option.type || this.option.type, 37 | mainCsi: option.mainCsi || this.option.mainCsi, 38 | aidCsi: option.aidCsi || this.option.aidCsi, 39 | timeFilter: option.timeFilter || this.option.timeFilter, 40 | overTimeFilter: option.overTimeFilter || this.option.overTimeFilter, 41 | priceDecimal: option.priceDecimal || this.option.priceDecimal, 42 | data: (data || this.option.data).map(d => d), 43 | period: option.period || this.option.period, 44 | }; 45 | const lastRange = this.state.range; 46 | init.call(this, option); 47 | if (lastPeriod === this.option.period) { 48 | this.state.range = lastRange; 49 | } 50 | } else { 51 | this.option = { 52 | theme: option.theme || 'dark', 53 | fontSize: option.fontSize || 12, 54 | type: option.type || 'candle', 55 | mainCsi: option.mainCsi || 'ema', 56 | aidCsi: option.aidCsi, 57 | timeFilter: option.timeFilter || (t => new Date(t * 1000).toString('M/d/yyyy')), 58 | overTimeFilter: option.overTimeFilter || (t => new Date(t * 1000).toString('M/d/yyyy')), 59 | priceDecimal: option.priceDecimal === undefined ? 0 : option.priceDecimal, 60 | data: (data || []).map(d => d), 61 | period: option.period || 60 * 60, 62 | }; 63 | 64 | init.call(this, option); 65 | } 66 | } 67 | 68 | function init() { 69 | this.device = (navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) ? 'mb' : 'pc'; 70 | // 设置全局色彩 71 | const isDarkTheme = this.option.theme === 'dark'; 72 | this.colors = { 73 | background: isDarkTheme ? '#0e2029' : '#ebf5fa', 74 | splitLine: isDarkTheme ? '#33434b' : '#c2cacf', 75 | lightColor: isDarkTheme ? '#ddd' : '#666', 76 | textColor: isDarkTheme ? '#878f94' : '#333', 77 | currentTextColor: isDarkTheme ? '#cad8e0' : '#000', 78 | textFrameColor: isDarkTheme ? '#5d727a' : '#a0a8ad', 79 | greenColor: isDarkTheme ? '#66d430' : '#68d12c', 80 | redColor: isDarkTheme ? '#d11e37' : '#d11d38', 81 | ma30Color: isDarkTheme ? 'rgb(234, 177, 103)' : 'rgb(234, 177, 103)', 82 | ma7Color: isDarkTheme ? 'rgb(166, 206, 227)' : 'rgb(59, 187, 59)', 83 | macdColor: isDarkTheme ? 'rgb(208, 146, 209)' : 'rgb(208, 146, 209)', 84 | hairLine: isDarkTheme ? '#33434b' : 'd3dbe0', 85 | mobileBar: isDarkTheme ? '#343f4d' : '#fafafa', 86 | lineColor: isDarkTheme ? '#ccc' : '#333', 87 | lineHilightColor: isDarkTheme ? '#fff' : '#000', 88 | }; 89 | 90 | this.ctx = this.canvas.getContext('2d'); 91 | this.overCtx = this.overCanvas.getContext('2d'); 92 | this.ctx.font = this.option.fontSize * this.dpr + 'px Consolas, Monaco, monospace, sans-serif'; 93 | this.overCtx.font = this.option.fontSize * this.dpr + 'px Consolas, Monaco, monospace, sans-serif'; 94 | 95 | const yAxisWidth = this.setData(); 96 | 97 | const left = 10 * this.dpr; 98 | const right = 0 * this.dpr; 99 | const top = 40 * this.dpr; 100 | const bottom = 20 * this.dpr; 101 | const middle = 10 * this.dpr; 102 | 103 | const width = this.width; 104 | const height = this.height; 105 | 106 | if (!this.option.aidCsi) { 107 | this.proportion = 1; 108 | } else { 109 | this.proportion = 0.7; 110 | } 111 | 112 | let mainView = { 113 | x: left, 114 | y: top, 115 | w: width - yAxisWidth - left - right - middle, 116 | h: (height - top - bottom) * this.proportion, 117 | }; 118 | let mainYaxisView = { 119 | x: mainView.w + mainView.x + middle, 120 | y: mainView.y, 121 | w: yAxisWidth, 122 | h: mainView.h, 123 | }; 124 | let aidView = { 125 | x: mainView.x, 126 | y: mainView.y + mainView.h, 127 | w: mainView.w, 128 | h: (height - top - bottom) * (1 - this.proportion), 129 | }; 130 | let aidYaxisView = { 131 | x: mainYaxisView.x, 132 | y: aidView.y, 133 | w: yAxisWidth, 134 | h: aidView.h, 135 | }; 136 | let timeView = { 137 | x: mainView.x, 138 | y: aidView.y + aidView.h, 139 | w: aidView.x + aidView.w + middle, 140 | h: bottom, 141 | }; 142 | this.mainView = mainView; 143 | this.mainYaxisView = mainYaxisView; 144 | this.aidView = aidView; 145 | this.aidYaxisView = aidYaxisView; 146 | this.timeView = timeView; 147 | 148 | this.maxVerticalRectNumber = parseInt(mainView.w / this.dpr / 2) % 2 === 0 ? parseInt(mainView.w / this.dpr / 2) : parseInt(mainView.w / this.dpr / 2) + 1; 149 | this.minVerticalRectNumber = 30; 150 | this.force = [true, true]; 151 | 152 | this.lines = this.lines.length === 0 ? [] : this.lines; 153 | this.lineCache = this.lineCache || null; 154 | this.mousePos = {}; 155 | } 156 | -------------------------------------------------------------------------------- /src2/tools/Arrow.js: -------------------------------------------------------------------------------- 1 | export default function ParallelSegment(ctx, colors, context) { 2 | this.ctx = ctx; 3 | this.colors = colors; 4 | this.step = 0; 5 | this.context = context; 6 | this.index = []; 7 | this.price = []; 8 | this.moving = false; 9 | } 10 | 11 | ParallelSegment.prototype.draw = function() { 12 | if (this.index.length === 0) { 13 | return; 14 | } 15 | const ctx = this.ctx; 16 | const [point1, point2] = this.getPos(); 17 | 18 | const linePath = this.getLine(); 19 | const arrowPath = this.getArrow(); 20 | if (this.isInPath(this.context.mousePos, linePath)) { 21 | ctx.lineWidth = this.context.dpr; 22 | if (this.step === 2) { 23 | ctx.strokeStyle = this.colors.lineHilightColor; 24 | } else { 25 | ctx.strokeStyle = this.colors.lineColor; 26 | } 27 | ctx.stroke(linePath); 28 | ctx.stroke(arrowPath); 29 | this.drawPoint(); 30 | } else { 31 | ctx.lineWidth = this.context.dpr; 32 | ctx.strokeStyle = this.colors.lineColor; 33 | ctx.stroke(linePath); 34 | ctx.stroke(arrowPath); 35 | } 36 | 37 | 38 | if (this.step !== 2) { 39 | this.drawPoint(); 40 | } 41 | }; 42 | 43 | ParallelSegment.prototype.next = function() { 44 | if (this.step === 0) { 45 | this.step = 1; 46 | } else if (this.step === 1) { 47 | this.step = 2; 48 | return true; 49 | } 50 | }; 51 | 52 | ParallelSegment.prototype.isInPath = function(pos, path) { 53 | const ctx = this.ctx; 54 | ctx.lineWidth = this.context.dpr * 10; 55 | if (!path) { 56 | path = this.getLine(); 57 | } 58 | if (ctx.isPointInStroke(path, pos.x, pos.y)) { 59 | return true; 60 | } 61 | return false; 62 | }; 63 | 64 | ParallelSegment.prototype.getArrow = function() { 65 | const [point1, point2] = this.getPos(); 66 | 67 | const angle = Math.atan2(point1.y - point2.y, point1.x - point2.x); 68 | const angle1 = angle + Math.PI / 6; 69 | const angle2 = angle - Math.PI / 6; 70 | const x1 = point2.x + 12 * Math.cos(angle1); 71 | const y1 = point2.y + 12 * Math.sin(angle1); 72 | const x2 = point2.x + 12 * Math.cos(angle2); 73 | const y2 = point2.y + 12 * Math.sin(angle2); 74 | 75 | const arrow = new Path2D(); 76 | arrow.moveTo(x1, y1); 77 | arrow.lineTo(point2.x, point2.y); 78 | arrow.lineTo(x2, y2); 79 | 80 | return arrow; 81 | }; 82 | 83 | ParallelSegment.prototype.getCircle = function() { 84 | const ctx = this.ctx; 85 | const [point1, point2] = this.getPos(); 86 | 87 | const circle1 = new Path2D(); 88 | circle1.arc(point1.x, point1.y, 5 * this.context.dpr, 0, Math.PI * 2); 89 | 90 | const circle2 = new Path2D(); 91 | circle2.arc(point2.x, point2.y, 5 * this.context.dpr, 0, Math.PI * 2); 92 | 93 | return [circle1, circle2]; 94 | }; 95 | 96 | ParallelSegment.prototype.getLine = function() { 97 | const ctx = this.ctx; 98 | const [point1, point2] = this.getPos(); 99 | 100 | const path = new Path2D(); 101 | path.moveTo(point1.x, point1.y); 102 | path.lineTo(point2.x, point2.y); 103 | 104 | return path; 105 | }; 106 | 107 | ParallelSegment.prototype.drawPoint = function() { 108 | const ctx = this.ctx; 109 | const [point1, point2] = this.getPos(); 110 | const [circle1, circle2] = this.getCircle(); 111 | 112 | ctx.lineWidth = this.context.dpr; 113 | ctx.fillStyle = this.colors.background; 114 | 115 | ctx.fill(circle1); 116 | ctx.stroke(circle1); 117 | 118 | ctx.fill(circle2); 119 | ctx.stroke(circle2); 120 | }; 121 | 122 | ParallelSegment.prototype.setPosition = function(index, price) { 123 | if (this.step === 0) { 124 | this.index = [index, index]; 125 | this.price = [price, price]; 126 | } else if (this.step === 1) { 127 | this.index = [this.index[0], index]; 128 | this.price = [this.price[0], price]; 129 | } 130 | }; 131 | 132 | /** 133 | * index 沿x轴移动的距离 134 | * price 当前价格 135 | */ 136 | ParallelSegment.prototype.move = function(index, price) { 137 | this.index = [this.index[0] + index, this.index[1] + index]; 138 | this.price = [this.price[0] + price, this.price[1] + price]; 139 | }; 140 | 141 | ParallelSegment.prototype.getPos = function() { 142 | const { mainView } = this.context; 143 | const [startIndex, endIndex] = this.context.state.range; 144 | const verticalRectNumber = endIndex - startIndex; 145 | const { max, min } = this.context.computAxis(); 146 | 147 | const pos = []; 148 | this.index.forEach((el, i) => { 149 | const x = (el - startIndex) * mainView.w / verticalRectNumber + mainView.w / verticalRectNumber * 0.5 + mainView.x; 150 | const y = mainView.y + (max - this.price[i]) / (max - min) * mainView.h; 151 | pos.push({ x, y }); 152 | }); 153 | return pos; 154 | }; 155 | -------------------------------------------------------------------------------- /src2/tools/Beam.js: -------------------------------------------------------------------------------- 1 | export default function Beam(ctx, colors, context) { 2 | this.ctx = ctx; 3 | this.colors = colors; 4 | this.step = 0; 5 | this.context = context; 6 | this.index = []; 7 | this.price = []; 8 | this.moving = false; 9 | } 10 | 11 | Beam.prototype.draw = function() { 12 | if (this.index.length === 0) { 13 | return; 14 | } 15 | const ctx = this.ctx; 16 | const [point1, point2] = this.getPos(); 17 | 18 | const linePath = this.getLine(); 19 | if (this.isInPath(this.context.mousePos, linePath)) { 20 | ctx.lineWidth = this.context.dpr; 21 | if (this.step === 2) { 22 | ctx.strokeStyle = this.colors.lineHilightColor; 23 | } else { 24 | ctx.strokeStyle = this.colors.lineColor; 25 | } 26 | ctx.stroke(linePath); 27 | this.drawPoint(); 28 | } else { 29 | ctx.lineWidth = this.context.dpr; 30 | ctx.strokeStyle = this.colors.lineColor; 31 | ctx.stroke(linePath); 32 | } 33 | 34 | if (this.step !== 2) { 35 | this.drawPoint(); 36 | } 37 | }; 38 | 39 | Beam.prototype.next = function() { 40 | if (this.step === 0) { 41 | this.step = 1; 42 | } else if (this.step === 1) { 43 | this.step = 2; 44 | return true; 45 | } 46 | }; 47 | 48 | Beam.prototype.isInPath = function(pos, path) { 49 | const ctx = this.ctx; 50 | ctx.lineWidth = this.context.dpr * 10; 51 | if (!path) { 52 | path = this.getLine(); 53 | } 54 | if (ctx.isPointInStroke(path, pos.x, pos.y)) { 55 | return true; 56 | } 57 | return false; 58 | }; 59 | 60 | Beam.prototype.getCircle = function() { 61 | const ctx = this.ctx; 62 | const [point1, point2] = this.getPos(); 63 | 64 | const circle1 = new Path2D(); 65 | circle1.arc(point1.x, point1.y, 5 * this.context.dpr, 0, Math.PI * 2); 66 | 67 | const circle2 = new Path2D(); 68 | circle2.arc(point2.x, point2.y, 5 * this.context.dpr, 0, Math.PI * 2); 69 | 70 | return [circle1, circle2]; 71 | }; 72 | 73 | Beam.prototype.getLine = function() { 74 | const ctx = this.ctx; 75 | const [point1, point2] = this.getPos(); 76 | 77 | const f = (point2.x - point1.x) / (point2.y - point1.y); 78 | 79 | let y; 80 | if (point2.y > point1.y) { 81 | y = this.context.mainView.y + this.context.mainView.h + 10; 82 | } else { 83 | y = 0; 84 | } 85 | const x = f * (y - point1.y) + point1.x; 86 | 87 | const path = new Path2D(); 88 | path.moveTo(point1.x, point1.y); 89 | path.lineTo(x, y); 90 | 91 | return path; 92 | }; 93 | 94 | Beam.prototype.drawPoint = function() { 95 | const ctx = this.ctx; 96 | const [point1, point2] = this.getPos(); 97 | const [circle1, circle2] = this.getCircle(); 98 | 99 | ctx.lineWidth = this.context.dpr; 100 | ctx.fillStyle = this.colors.background; 101 | 102 | ctx.fill(circle1); 103 | ctx.stroke(circle1); 104 | 105 | ctx.fill(circle2); 106 | ctx.stroke(circle2); 107 | }; 108 | 109 | Beam.prototype.setPosition = function(index, price) { 110 | if (this.step === 0) { 111 | this.index = [index, index]; 112 | this.price = [price, price]; 113 | } else if (this.step === 1) { 114 | this.index = [this.index[0], index]; 115 | this.price = [this.price[0], price]; 116 | } 117 | }; 118 | 119 | /** 120 | * index 沿x轴移动的距离 121 | * price 当前价格 122 | */ 123 | Beam.prototype.move = function(index, price) { 124 | this.index = [this.index[0] + index, this.index[1] + index]; 125 | this.price = [this.price[0] + price, this.price[1] + price]; 126 | }; 127 | 128 | Beam.prototype.getPos = function() { 129 | const { mainView } = this.context; 130 | const [startIndex, endIndex] = this.context.state.range; 131 | const verticalRectNumber = endIndex - startIndex; 132 | const { max, min } = this.context.computAxis(); 133 | 134 | const pos = []; 135 | this.index.forEach((el, i) => { 136 | const x = (el - startIndex) * mainView.w / verticalRectNumber + mainView.w / verticalRectNumber * 0.5 + mainView.x; 137 | const y = mainView.y + (max - this.price[i]) / (max - min) * mainView.h; 138 | pos.push({ x, y }); 139 | }); 140 | return pos; 141 | }; 142 | -------------------------------------------------------------------------------- /src2/tools/HorizontalBeam.js: -------------------------------------------------------------------------------- 1 | export default function HorizontalBeam(ctx, colors, context) { 2 | this.ctx = ctx; 3 | this.colors = colors; 4 | this.step = 0; 5 | this.context = context; 6 | this.index = []; 7 | this.price = []; 8 | this.moving = false; 9 | } 10 | 11 | HorizontalBeam.prototype.draw = function() { 12 | if (this.index.length === 0) { 13 | return; 14 | } 15 | const ctx = this.ctx; 16 | 17 | const linePath = this.getLine(); 18 | if (this.isInPath(this.context.mousePos, linePath)) { 19 | ctx.lineWidth = this.context.dpr; 20 | if (this.step === 2) { 21 | ctx.strokeStyle = this.colors.lineHilightColor; 22 | } else { 23 | ctx.strokeStyle = this.colors.lineColor; 24 | } 25 | ctx.stroke(linePath); 26 | this.drawPoint(); 27 | } else { 28 | ctx.lineWidth = this.context.dpr; 29 | ctx.strokeStyle = this.colors.lineColor; 30 | ctx.stroke(linePath); 31 | } 32 | 33 | if (this.step !== 2) { 34 | this.drawPoint(); 35 | } 36 | }; 37 | 38 | HorizontalBeam.prototype.next = function() { 39 | if (this.step === 0) { 40 | this.step = 1; 41 | } else if (this.step === 1) { 42 | this.step = 2; 43 | return true; 44 | } 45 | }; 46 | 47 | HorizontalBeam.prototype.isInPath = function(pos, path) { 48 | const ctx = this.ctx; 49 | ctx.lineWidth = this.context.dpr * 10; 50 | if (!path) { 51 | path = this.getLine(); 52 | } 53 | if (ctx.isPointInStroke(path, pos.x, pos.y)) { 54 | return true; 55 | } 56 | return false; 57 | }; 58 | 59 | HorizontalBeam.prototype.getCircle = function() { 60 | const ctx = this.ctx; 61 | const [point1, point2] = this.getPos(); 62 | 63 | const circle1 = new Path2D(); 64 | circle1.arc(point1.x, point2.y, 5 * this.context.dpr, 0, Math.PI * 2); 65 | 66 | const circle2 = new Path2D(); 67 | circle2.arc(point2.x, point2.y, 5 * this.context.dpr, 0, Math.PI * 2); 68 | 69 | return [circle1, circle2]; 70 | }; 71 | 72 | HorizontalBeam.prototype.getLine = function() { 73 | const ctx = this.ctx; 74 | const [point1, point2] = this.getPos(); 75 | 76 | const path = new Path2D(); 77 | path.moveTo(point1.x, point2.y); 78 | if (point2.x >= point1.x) { 79 | path.lineTo(this.context.mainYaxisView.x, point2.y); 80 | } else { 81 | path.lineTo(0, point2.y); 82 | } 83 | 84 | return path; 85 | }; 86 | 87 | HorizontalBeam.prototype.drawPoint = function() { 88 | const ctx = this.ctx; 89 | const [point1, point2] = this.getPos(); 90 | const [circle1, circle2] = this.getCircle(); 91 | 92 | ctx.lineWidth = this.context.dpr; 93 | ctx.fillStyle = this.colors.background; 94 | 95 | ctx.fill(circle1); 96 | ctx.stroke(circle1); 97 | 98 | ctx.fill(circle2); 99 | ctx.stroke(circle2); 100 | }; 101 | 102 | HorizontalBeam.prototype.setPosition = function(index, price) { 103 | if (this.step === 0) { 104 | this.index = [index, index]; 105 | this.price = [price, price]; 106 | } else if (this.step === 1) { 107 | this.index = [this.index[0], index]; 108 | this.price = [this.price[0], price]; 109 | } 110 | }; 111 | 112 | /** 113 | * index 沿x轴移动的距离 114 | * price 当前价格 115 | */ 116 | HorizontalBeam.prototype.move = function(index, price) { 117 | this.index = [this.index[0] + index, this.index[1] + index]; 118 | this.price = [this.price[0] + price, this.price[1] + price]; 119 | }; 120 | 121 | HorizontalBeam.prototype.getPos = function() { 122 | const { mainView } = this.context; 123 | const [startIndex, endIndex] = this.context.state.range; 124 | const verticalRectNumber = endIndex - startIndex; 125 | const { max, min } = this.context.computAxis(); 126 | 127 | const pos = []; 128 | this.index.forEach((el, i) => { 129 | const x = (el - startIndex) * mainView.w / verticalRectNumber + mainView.w / verticalRectNumber * 0.5 + mainView.x; 130 | const y = mainView.y + (max - this.price[i]) / (max - min) * mainView.h; 131 | pos.push({ x, y }); 132 | }); 133 | return pos; 134 | }; 135 | -------------------------------------------------------------------------------- /src2/tools/HorizontalLine.js: -------------------------------------------------------------------------------- 1 | export default function HorizontalLine(ctx, colors, context) { 2 | this.ctx = ctx; 3 | this.colors = colors; 4 | this.step = 0; 5 | this.context = context; 6 | this.index = -1; 7 | this.price = -1; 8 | this.moving = false; 9 | } 10 | 11 | HorizontalLine.prototype.draw = function() { 12 | if (this.index.length === 0) { 13 | return; 14 | } 15 | const ctx = this.ctx; 16 | 17 | const linePath = this.getLine(); 18 | if (this.isInPath(this.context.mousePos, linePath)) { 19 | ctx.lineWidth = this.context.dpr; 20 | if (this.step === 1) { 21 | ctx.strokeStyle = this.colors.lineHilightColor; 22 | } else { 23 | ctx.strokeStyle = this.colors.lineColor; 24 | } 25 | ctx.stroke(linePath); 26 | this.drawPoint(); 27 | } else { 28 | ctx.lineWidth = this.context.dpr; 29 | ctx.strokeStyle = this.colors.lineColor; 30 | ctx.stroke(linePath); 31 | } 32 | 33 | if (this.step !== 1) { 34 | this.drawPoint(); 35 | } 36 | }; 37 | 38 | HorizontalLine.prototype.next = function() { 39 | if (this.step === 0) { 40 | this.step = 1; 41 | return true; 42 | } 43 | }; 44 | 45 | HorizontalLine.prototype.isInPath = function(pos, path) { 46 | const ctx = this.ctx; 47 | ctx.lineWidth = this.context.dpr * 10; 48 | if (!path) { 49 | path = this.getLine(); 50 | } 51 | if (ctx.isPointInStroke(path, pos.x, pos.y)) { 52 | return true; 53 | } 54 | return false; 55 | }; 56 | 57 | HorizontalLine.prototype.getCircle = function() { 58 | const ctx = this.ctx; 59 | const point = this.getPos(); 60 | 61 | const circle = new Path2D(); 62 | circle.arc(point.x, point.y, 5 * this.context.dpr, 0, Math.PI * 2); 63 | 64 | return circle; 65 | }; 66 | 67 | HorizontalLine.prototype.getLine = function() { 68 | const ctx = this.ctx; 69 | const point = this.getPos(); 70 | 71 | const path = new Path2D(); 72 | path.moveTo(0, point.y); 73 | path.lineTo(this.context.mainYaxisView.x, point.y); 74 | 75 | return path; 76 | }; 77 | 78 | HorizontalLine.prototype.drawPoint = function() { 79 | const ctx = this.ctx; 80 | const circle = this.getCircle(); 81 | 82 | ctx.lineWidth = this.context.dpr; 83 | ctx.fillStyle = this.colors.background; 84 | 85 | ctx.fill(circle); 86 | ctx.stroke(circle); 87 | }; 88 | 89 | HorizontalLine.prototype.setPosition = function(index, price) { 90 | if (this.step === 0) { 91 | this.index = index; 92 | this.price = price; 93 | } 94 | }; 95 | 96 | HorizontalLine.prototype.move = function(index, price) { 97 | this.index += index; 98 | this.price += price; 99 | }; 100 | 101 | HorizontalLine.prototype.getPos = function() { 102 | const { mainView } = this.context; 103 | const [startIndex, endIndex] = this.context.state.range; 104 | const verticalRectNumber = endIndex - startIndex; 105 | const { max, min } = this.context.computAxis(); 106 | 107 | const x = (this.index - startIndex) * mainView.w / verticalRectNumber + mainView.w / verticalRectNumber * 0.5 + mainView.x; 108 | const y = mainView.y + (max - this.price) / (max - min) * mainView.h; 109 | return { x, y }; 110 | }; 111 | -------------------------------------------------------------------------------- /src2/tools/Line.js: -------------------------------------------------------------------------------- 1 | export default function Line(ctx, colors, context) { 2 | this.ctx = ctx; 3 | this.colors = colors; 4 | this.step = 0; 5 | this.context = context; 6 | this.index = []; 7 | this.price = []; 8 | this.moving = false; 9 | } 10 | 11 | Line.prototype.draw = function() { 12 | if (this.index.length === 0) { 13 | return; 14 | } 15 | const ctx = this.ctx; 16 | const [point1, point2] = this.getPos(); 17 | 18 | const linePath = this.getLine(); 19 | if (this.isInPath(this.context.mousePos, linePath)) { 20 | ctx.lineWidth = this.context.dpr; 21 | if (this.step === 2) { 22 | ctx.strokeStyle = this.colors.lineHilightColor; 23 | } else { 24 | ctx.strokeStyle = this.colors.lineColor; 25 | } 26 | ctx.stroke(linePath); 27 | this.drawPoint(); 28 | } else { 29 | ctx.lineWidth = this.context.dpr; 30 | ctx.strokeStyle = this.colors.lineColor; 31 | ctx.stroke(linePath); 32 | } 33 | 34 | if (this.step !== 2) { 35 | this.drawPoint(); 36 | } 37 | }; 38 | 39 | Line.prototype.next = function() { 40 | if (this.step === 0) { 41 | this.step = 1; 42 | } else if (this.step === 1) { 43 | this.step = 2; 44 | return true; 45 | } 46 | }; 47 | 48 | Line.prototype.isInPath = function(pos, path) { 49 | const ctx = this.ctx; 50 | ctx.lineWidth = this.context.dpr * 10; 51 | if (!path) { 52 | path = this.getLine(); 53 | } 54 | if (ctx.isPointInStroke(path, pos.x, pos.y)) { 55 | return true; 56 | } 57 | return false; 58 | }; 59 | 60 | Line.prototype.getCircle = function() { 61 | const ctx = this.ctx; 62 | const [point1, point2] = this.getPos(); 63 | 64 | const circle1 = new Path2D(); 65 | circle1.arc(point1.x, point1.y, 5 * this.context.dpr, 0, Math.PI * 2); 66 | 67 | const circle2 = new Path2D(); 68 | circle2.arc(point2.x, point2.y, 5 * this.context.dpr, 0, Math.PI * 2); 69 | 70 | return [circle1, circle2]; 71 | }; 72 | 73 | Line.prototype.getLine = function() { 74 | const ctx = this.ctx; 75 | const [point1, point2] = this.getPos(); 76 | 77 | const f = (point2.x - point1.x) / (point2.y - point1.y); 78 | 79 | const y1 = 0; 80 | const x1 = f * (y1 - point1.y) + point1.x; 81 | const y2 = this.context.mainView.y + this.context.mainView.h + 10; 82 | const x2 = f * (y2 - point1.y) + point1.x; 83 | 84 | const path = new Path2D(); 85 | path.moveTo(x1, y1); 86 | path.lineTo(x2, y2); 87 | 88 | return path; 89 | }; 90 | 91 | Line.prototype.drawPoint = function() { 92 | const ctx = this.ctx; 93 | const [point1, point2] = this.getPos(); 94 | const [circle1, circle2] = this.getCircle(); 95 | 96 | ctx.lineWidth = this.context.dpr; 97 | ctx.fillStyle = this.colors.background; 98 | 99 | ctx.fill(circle1); 100 | ctx.stroke(circle1); 101 | 102 | ctx.fill(circle2); 103 | ctx.stroke(circle2); 104 | }; 105 | 106 | Line.prototype.setPosition = function(index, price) { 107 | if (this.step === 0) { 108 | this.index = [index, index]; 109 | this.price = [price, price]; 110 | } else if (this.step === 1) { 111 | this.index = [this.index[0], index]; 112 | this.price = [this.price[0], price]; 113 | } 114 | }; 115 | 116 | /** 117 | * index 沿x轴移动的距离 118 | * price 当前价格 119 | */ 120 | Line.prototype.move = function(index, price) { 121 | this.index = [this.index[0] + index, this.index[1] + index]; 122 | this.price = [this.price[0] + price, this.price[1] + price]; 123 | }; 124 | 125 | Line.prototype.getPos = function() { 126 | const { mainView } = this.context; 127 | const [startIndex, endIndex] = this.context.state.range; 128 | const verticalRectNumber = endIndex - startIndex; 129 | const { max, min } = this.context.computAxis(); 130 | 131 | const pos = []; 132 | this.index.forEach((el, i) => { 133 | const x = (el - startIndex) * mainView.w / verticalRectNumber + mainView.w / verticalRectNumber * 0.5 + mainView.x; 134 | const y = mainView.y + (max - this.price[i]) / (max - min) * mainView.h; 135 | pos.push({ x, y }); 136 | }); 137 | return pos; 138 | }; 139 | -------------------------------------------------------------------------------- /src2/tools/ParallelSegment.js: -------------------------------------------------------------------------------- 1 | export default function ParallelSegment(ctx, colors, context) { 2 | this.ctx = ctx; 3 | this.colors = colors; 4 | this.step = 0; 5 | this.context = context; 6 | this.index = []; 7 | this.price = []; 8 | this.moving = false; 9 | } 10 | 11 | ParallelSegment.prototype.draw = function() { 12 | if (this.index.length === 0) { 13 | return; 14 | } 15 | const ctx = this.ctx; 16 | const [point1, point2] = this.getPos(); 17 | 18 | const linePath = this.getLine(); 19 | if (this.isInPath(this.context.mousePos, linePath)) { 20 | ctx.lineWidth = this.context.dpr; 21 | if (this.step === 2) { 22 | ctx.strokeStyle = this.colors.lineHilightColor; 23 | } else { 24 | ctx.strokeStyle = this.colors.lineColor; 25 | } 26 | ctx.stroke(linePath); 27 | this.drawPoint(); 28 | } else { 29 | ctx.lineWidth = this.context.dpr; 30 | ctx.strokeStyle = this.colors.lineColor; 31 | ctx.stroke(linePath); 32 | } 33 | 34 | if (this.step !== 2) { 35 | this.drawPoint(); 36 | } 37 | }; 38 | 39 | ParallelSegment.prototype.next = function() { 40 | if (this.step === 0) { 41 | this.step = 1; 42 | } else if (this.step === 1) { 43 | this.step = 2; 44 | return true; 45 | } 46 | }; 47 | 48 | ParallelSegment.prototype.isInPath = function(pos, path) { 49 | const ctx = this.ctx; 50 | ctx.lineWidth = this.context.dpr * 10; 51 | if (!path) { 52 | path = this.getLine(); 53 | } 54 | if (ctx.isPointInStroke(path, pos.x, pos.y)) { 55 | return true; 56 | } 57 | return false; 58 | }; 59 | 60 | ParallelSegment.prototype.getCircle = function() { 61 | const ctx = this.ctx; 62 | const [point1, point2] = this.getPos(); 63 | 64 | const circle1 = new Path2D(); 65 | circle1.arc(point1.x, point2.y, 5 * this.context.dpr, 0, Math.PI * 2); 66 | 67 | const circle2 = new Path2D(); 68 | circle2.arc(point2.x, point2.y, 5 * this.context.dpr, 0, Math.PI * 2); 69 | 70 | return [circle1, circle2]; 71 | }; 72 | 73 | ParallelSegment.prototype.getLine = function() { 74 | const ctx = this.ctx; 75 | const [point1, point2] = this.getPos(); 76 | 77 | const path = new Path2D(); 78 | path.moveTo(point1.x, point2.y); 79 | path.lineTo(point2.x, point2.y); 80 | 81 | return path; 82 | }; 83 | 84 | ParallelSegment.prototype.drawPoint = function() { 85 | const ctx = this.ctx; 86 | const [point1, point2] = this.getPos(); 87 | const [circle1, circle2] = this.getCircle(); 88 | 89 | ctx.lineWidth = this.context.dpr; 90 | ctx.fillStyle = this.colors.background; 91 | 92 | ctx.fill(circle1); 93 | ctx.stroke(circle1); 94 | 95 | ctx.fill(circle2); 96 | ctx.stroke(circle2); 97 | }; 98 | 99 | ParallelSegment.prototype.setPosition = function(index, price) { 100 | if (this.step === 0) { 101 | this.index = [index, index]; 102 | this.price = [price, price]; 103 | } else if (this.step === 1) { 104 | this.index = [this.index[0], index]; 105 | this.price = [this.price[0], price]; 106 | } 107 | }; 108 | 109 | /** 110 | * index 沿x轴移动的距离 111 | * price 当前价格 112 | */ 113 | ParallelSegment.prototype.move = function(index, price) { 114 | this.index = [this.index[0] + index, this.index[1] + index]; 115 | this.price = [this.price[0] + price, this.price[1] + price]; 116 | }; 117 | 118 | ParallelSegment.prototype.getPos = function() { 119 | const { mainView } = this.context; 120 | const [startIndex, endIndex] = this.context.state.range; 121 | const verticalRectNumber = endIndex - startIndex; 122 | const { max, min } = this.context.computAxis(); 123 | 124 | const pos = []; 125 | this.index.forEach((el, i) => { 126 | const x = (el - startIndex) * mainView.w / verticalRectNumber + mainView.w / verticalRectNumber * 0.5 + mainView.x; 127 | const y = mainView.y + (max - this.price[i]) / (max - min) * mainView.h; 128 | pos.push({ x, y }); 129 | }); 130 | return pos; 131 | }; 132 | -------------------------------------------------------------------------------- /src2/tools/PriceLine.js: -------------------------------------------------------------------------------- 1 | export default function PriceLine(ctx, colors, context) { 2 | this.ctx = ctx; 3 | this.colors = colors; 4 | this.step = 0; 5 | this.context = context; 6 | this.index = -1; 7 | this.price = -1; 8 | this.moving = false; 9 | } 10 | 11 | PriceLine.prototype.draw = function() { 12 | if (this.index.length === 0) { 13 | return; 14 | } 15 | const ctx = this.ctx; 16 | const point = this.getPos(); 17 | 18 | const linePath = this.getLine(); 19 | if (this.isInPath(this.context.mousePos, linePath)) { 20 | ctx.lineWidth = this.context.dpr; 21 | if (this.step === 1) { 22 | ctx.strokeStyle = this.colors.lineHilightColor; 23 | ctx.fillStyle = this.colors.lineHilightColor; 24 | } else { 25 | ctx.strokeStyle = this.colors.lineColor; 26 | ctx.fillStyle = this.colors.lineColor; 27 | } 28 | ctx.stroke(linePath); 29 | ctx.fillText(this.price.toFixed(this.context.option.priceDecimal), point.x, point.y); 30 | this.drawPoint(); 31 | } else { 32 | ctx.lineWidth = this.context.dpr; 33 | ctx.strokeStyle = this.colors.lineColor; 34 | ctx.fillStyle = this.colors.lineColor; 35 | ctx.stroke(linePath); 36 | ctx.fillText(this.price.toFixed(this.context.option.priceDecimal), point.x, point.y); 37 | } 38 | 39 | if (this.step !== 1) { 40 | this.drawPoint(); 41 | } 42 | }; 43 | 44 | PriceLine.prototype.next = function() { 45 | if (this.step === 0) { 46 | this.step = 1; 47 | return true; 48 | } 49 | }; 50 | 51 | PriceLine.prototype.isInPath = function(pos, path) { 52 | const ctx = this.ctx; 53 | ctx.lineWidth = this.context.dpr * 10; 54 | if (!path) { 55 | path = this.getLine(); 56 | } 57 | if (ctx.isPointInStroke(path, pos.x, pos.y)) { 58 | return true; 59 | } 60 | return false; 61 | }; 62 | 63 | PriceLine.prototype.getCircle = function() { 64 | const ctx = this.ctx; 65 | const point = this.getPos(); 66 | 67 | const circle = new Path2D(); 68 | circle.arc(point.x, point.y, 5 * this.context.dpr, 0, Math.PI * 2); 69 | 70 | return circle; 71 | }; 72 | 73 | PriceLine.prototype.getLine = function() { 74 | const ctx = this.ctx; 75 | const point = this.getPos(); 76 | 77 | const path = new Path2D(); 78 | path.moveTo(point.x, point.y); 79 | path.lineTo(this.context.mainYaxisView.x, point.y); 80 | 81 | return path; 82 | }; 83 | 84 | PriceLine.prototype.drawPoint = function() { 85 | const ctx = this.ctx; 86 | const circle = this.getCircle(); 87 | 88 | ctx.lineWidth = this.context.dpr; 89 | ctx.fillStyle = this.colors.background; 90 | 91 | ctx.fill(circle); 92 | ctx.stroke(circle); 93 | }; 94 | 95 | PriceLine.prototype.setPosition = function(index, price) { 96 | if (this.step === 0) { 97 | this.index = index; 98 | this.price = price; 99 | } 100 | }; 101 | 102 | PriceLine.prototype.move = function(index, price) { 103 | this.index += index; 104 | this.price += price; 105 | }; 106 | 107 | PriceLine.prototype.getPos = function() { 108 | const { mainView } = this.context; 109 | const [startIndex, endIndex] = this.context.state.range; 110 | const verticalRectNumber = endIndex - startIndex; 111 | const { max, min } = this.context.computAxis(); 112 | 113 | const x = (this.index - startIndex) * mainView.w / verticalRectNumber + mainView.w / verticalRectNumber * 0.5 + mainView.x; 114 | const y = mainView.y + (max - this.price) / (max - min) * mainView.h; 115 | return { x, y }; 116 | }; 117 | -------------------------------------------------------------------------------- /src2/tools/Segment.js: -------------------------------------------------------------------------------- 1 | export default function Segment(ctx, colors, context) { 2 | this.ctx = ctx; 3 | this.colors = colors; 4 | this.step = 0; 5 | this.context = context; 6 | this.index = []; 7 | this.price = []; 8 | this.moving = false; 9 | } 10 | 11 | Segment.prototype.draw = function() { 12 | if (this.index.length === 0) { 13 | return; 14 | } 15 | const ctx = this.ctx; 16 | const [point1, point2] = this.getPos(); 17 | 18 | const linePath = this.getLine(); 19 | if (this.isInPath(this.context.mousePos, linePath)) { 20 | ctx.lineWidth = this.context.dpr; 21 | if (this.step === 2) { 22 | ctx.strokeStyle = this.colors.lineHilightColor; 23 | } else { 24 | ctx.strokeStyle = this.colors.lineColor; 25 | } 26 | ctx.stroke(linePath); 27 | this.drawPoint(); 28 | } else { 29 | ctx.lineWidth = this.context.dpr; 30 | ctx.strokeStyle = this.colors.lineColor; 31 | ctx.stroke(linePath); 32 | } 33 | 34 | if (this.step !== 2) { 35 | this.drawPoint(); 36 | } 37 | }; 38 | 39 | Segment.prototype.next = function() { 40 | if (this.step === 0) { 41 | this.step = 1; 42 | } else if (this.step === 1) { 43 | this.step = 2; 44 | return true; 45 | } 46 | }; 47 | 48 | Segment.prototype.isInPath = function(pos, path) { 49 | const ctx = this.ctx; 50 | ctx.lineWidth = this.context.dpr * 10; 51 | if (!path) { 52 | path = this.getLine(); 53 | } 54 | if (ctx.isPointInStroke(path, pos.x, pos.y)) { 55 | return true; 56 | } 57 | return false; 58 | }; 59 | 60 | Segment.prototype.getCircle = function() { 61 | const ctx = this.ctx; 62 | const [point1, point2] = this.getPos(); 63 | 64 | const circle1 = new Path2D(); 65 | circle1.arc(point1.x, point1.y, 5 * this.context.dpr, 0, Math.PI * 2); 66 | 67 | const circle2 = new Path2D(); 68 | circle2.arc(point2.x, point2.y, 5 * this.context.dpr, 0, Math.PI * 2); 69 | 70 | return [circle1, circle2]; 71 | }; 72 | 73 | Segment.prototype.getLine = function() { 74 | const ctx = this.ctx; 75 | const [point1, point2] = this.getPos(); 76 | 77 | const path = new Path2D(); 78 | path.moveTo(point1.x, point1.y); 79 | path.lineTo(point2.x, point2.y); 80 | 81 | return path; 82 | }; 83 | 84 | Segment.prototype.drawPoint = function() { 85 | const ctx = this.ctx; 86 | const [point1, point2] = this.getPos(); 87 | const [circle1, circle2] = this.getCircle(); 88 | 89 | ctx.lineWidth = this.context.dpr; 90 | ctx.fillStyle = this.colors.background; 91 | 92 | ctx.fill(circle1); 93 | ctx.stroke(circle1); 94 | 95 | ctx.fill(circle2); 96 | ctx.stroke(circle2); 97 | }; 98 | 99 | Segment.prototype.setPosition = function(index, price) { 100 | if (this.step === 0) { 101 | this.index = [index, index]; 102 | this.price = [price, price]; 103 | } else if (this.step === 1) { 104 | this.index = [this.index[0], index]; 105 | this.price = [this.price[0], price]; 106 | } 107 | }; 108 | 109 | /** 110 | * index 沿x轴移动的距离 111 | * price 当前价格 112 | */ 113 | Segment.prototype.move = function(index, price) { 114 | this.index = [this.index[0] + index, this.index[1] + index]; 115 | this.price = [this.price[0] + price, this.price[1] + price]; 116 | }; 117 | 118 | Segment.prototype.getPos = function() { 119 | const { mainView } = this.context; 120 | const [startIndex, endIndex] = this.context.state.range; 121 | const verticalRectNumber = endIndex - startIndex; 122 | const { max, min } = this.context.computAxis(); 123 | 124 | const pos = []; 125 | this.index.forEach((el, i) => { 126 | const x = (el - startIndex) * mainView.w / verticalRectNumber + mainView.w / verticalRectNumber * 0.5 + mainView.x; 127 | const y = mainView.y + (max - this.price[i]) / (max - min) * mainView.h; 128 | pos.push({ x, y }); 129 | }); 130 | return pos; 131 | }; 132 | -------------------------------------------------------------------------------- /src2/tools/VerticalLine.js: -------------------------------------------------------------------------------- 1 | export default function VerticalLine(ctx, colors, context) { 2 | this.ctx = ctx; 3 | this.colors = colors; 4 | this.step = 0; 5 | this.context = context; 6 | this.index = -1; 7 | this.price = -1; 8 | this.moving = false; 9 | } 10 | 11 | VerticalLine.prototype.draw = function() { 12 | if (this.index.length === 0) { 13 | return; 14 | } 15 | const ctx = this.ctx; 16 | 17 | const linePath = this.getLine(); 18 | if (this.isInPath(this.context.mousePos, linePath)) { 19 | ctx.lineWidth = this.context.dpr; 20 | if (this.step === 1) { 21 | ctx.strokeStyle = this.colors.lineHilightColor; 22 | } else { 23 | ctx.strokeStyle = this.colors.lineColor; 24 | } 25 | ctx.stroke(linePath); 26 | this.drawPoint(); 27 | } else { 28 | ctx.lineWidth = this.context.dpr; 29 | ctx.strokeStyle = this.colors.lineColor; 30 | ctx.stroke(linePath); 31 | } 32 | 33 | if (this.step !== 1) { 34 | this.drawPoint(); 35 | } 36 | }; 37 | 38 | VerticalLine.prototype.next = function() { 39 | if (this.step === 0) { 40 | this.step = 1; 41 | return true; 42 | } 43 | }; 44 | 45 | VerticalLine.prototype.isInPath = function(pos, path) { 46 | const ctx = this.ctx; 47 | ctx.lineWidth = this.context.dpr * 10; 48 | if (!path) { 49 | path = this.getLine(); 50 | } 51 | if (ctx.isPointInStroke(path, pos.x, pos.y)) { 52 | return true; 53 | } 54 | return false; 55 | }; 56 | 57 | VerticalLine.prototype.getCircle = function() { 58 | const ctx = this.ctx; 59 | const point = this.getPos(); 60 | 61 | const circle = new Path2D(); 62 | circle.arc(point.x, point.y, 5 * this.context.dpr, 0, Math.PI * 2); 63 | 64 | return circle; 65 | }; 66 | 67 | VerticalLine.prototype.getLine = function() { 68 | const ctx = this.ctx; 69 | const point = this.getPos(); 70 | 71 | const path = new Path2D(); 72 | path.moveTo(point.x, 0); 73 | path.lineTo(point.x, this.context.mainView.y + this.context.mainView.h + 10); 74 | 75 | return path; 76 | }; 77 | 78 | VerticalLine.prototype.drawPoint = function() { 79 | const ctx = this.ctx; 80 | const circle = this.getCircle(); 81 | 82 | ctx.lineWidth = this.context.dpr; 83 | ctx.fillStyle = this.colors.background; 84 | 85 | ctx.fill(circle); 86 | ctx.stroke(circle); 87 | }; 88 | 89 | VerticalLine.prototype.setPosition = function(index, price) { 90 | if (this.step === 0) { 91 | this.index = index; 92 | this.price = price; 93 | } 94 | }; 95 | 96 | VerticalLine.prototype.move = function(index, price) { 97 | this.index += index; 98 | this.price += price; 99 | }; 100 | 101 | VerticalLine.prototype.getPos = function() { 102 | const { mainView } = this.context; 103 | const [startIndex, endIndex] = this.context.state.range; 104 | const verticalRectNumber = endIndex - startIndex; 105 | const { max, min } = this.context.computAxis(); 106 | 107 | const x = (this.index - startIndex) * mainView.w / verticalRectNumber + mainView.w / verticalRectNumber * 0.5 + mainView.x; 108 | const y = mainView.y + (max - this.price) / (max - min) * mainView.h; 109 | return { x, y }; 110 | }; 111 | -------------------------------------------------------------------------------- /src2/tools/drawLineCache.js: -------------------------------------------------------------------------------- 1 | export default function drawLineCache() { 2 | this.lineCache.draw(); 3 | } 4 | -------------------------------------------------------------------------------- /src2/tools/drawLines.js: -------------------------------------------------------------------------------- 1 | export default function drawLines() { 2 | this.lines.forEach(line => { 3 | line.draw(); 4 | }); 5 | } 6 | -------------------------------------------------------------------------------- /webpack-build.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | entry: './src2/KLine.js', 3 | output: { 4 | filename: 'KLine.js', 5 | library: 'KLine', 6 | libraryTarget: 'umd', 7 | }, 8 | module: { 9 | rules: [{ 10 | test: /\.js$/, 11 | exclude: /node_modules/, 12 | loader: 'babel-loader' 13 | }] 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | module.exports = { 3 | entry: './src2/index.js', 4 | output: { 5 | path: path.join(__dirname, 'dist'), 6 | filename: 'KLine.js', 7 | publicPath: '/', 8 | }, 9 | devServer: { 10 | publicPath: '/dist', 11 | host: '0.0.0.0', 12 | disableHostCheck: true, 13 | }, 14 | module: { 15 | rules: [{ 16 | test: /\.js$/, 17 | exclude: /node_modules/, 18 | loader: 'babel-loader' 19 | }] 20 | } 21 | }; 22 | --------------------------------------------------------------------------------