├── .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 |
--------------------------------------------------------------------------------