# 引言

本次任务是需要在一个指标图上通过点击标记画出此标记参与计算的数据范围、最高最低值、参考线等等,于是有了以下代码。代码仅供参考,如有错误的地方请指正!
7fd440be0502d0099427e7c74c4ebf29.jpg

# 模版代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// 箱体指标
const boxDataScope = 300 //箱体范围
// 黄金线参数
const goldenSectionA = 0.191
const goldenSectionB = 0.382
const goldenSectionC = 0.5
const goldenSectionD = 0.618
const goldenSectionE = 0.809
this.chart.addTechnicalIndicatorTemplate({
name: 'custom_box_solid',
shortName: '箱体',
precision: 2,
plots: [ // key属性的值最好在主图数据范围内,否则Y轴的值会跟着变大可能会导致主图变成一条线
{ key: 'max', title: '最高:' },
{ key: 'min', title: '最低:' }
],
calcParams: [boxDataScope, goldenSectionA, goldenSectionB, goldenSectionC, goldenSectionD, goldenSectionE],
calcTechnicalIndicator: (dataList, { params, plots }) => {
let allDatas = [] // 所有数据
let selectedDatas = [] //选中的数据
for (let i = 0; i < dataList.length; i++) {
let kLineData = dataList[i]
if (new Date(dateConvert(kLineData.timestamp)).getTime() === getGlobalObject('boxId')) {
const size = params[0]
// 找出当前数据往前的size条数据(包含自己)
let startData = i - size + 1
if (startData < 0) {
startData = 0
}
let endData = i + 1
if (endData > dataList.length) {
endData = dataList.length
}
selectedDatas = dataList.slice(startData, endData)
}
allDatas.push({
timestamp: kLineData.timestamp
})
}
// 找出选中数据中最高最低差价
let max, min
selectedDatas.forEach(function (item) {
let value = item.close - item.close2
if (!max || value > max) {
max = value
}
if (!min || value < min) {
min = value
}
})
// 返回指标最终数据(未选中的数据用空对象替换)
// 必须返回和dataList一样条数的数据,否则title不会显示
return allDatas.map((data, i) => {
let item = {
}
selectedDatas.map((selected, j) => {
if (data.timestamp === selected.timestamp) {
item.timestamp = selected.timestamp
item.max = max
item.min = min
}
})
return item
})
},
render: ({ ctx, dataSource, viewport, styles, xAxis, yAxis }) => {
if (dataSource.technicalIndicatorDataList.length <= 0) { // 无指标数据则不处理
return
}
// X轴起始像素
let x = xAxis.convertToPixel(0)
// 标记选中数据的起止位置
let start
let end = dataSource.technicalIndicatorDataList.length - 1
dataSource.technicalIndicatorDataList.forEach(function (kLineData, i) {
if (kLineData.timestamp) {
if (!start) {
start = i
}
if (i < end && !dataSource.technicalIndicatorDataList[i + 1].timestamp) {
end = i
}
let max = kLineData.max
let min = kLineData.min
ctx.fillStyle = '#fff'
ctx.textBaseline = 'middle'
ctx.textAlign = 'center'
// 画箱体
// 箱体颜色
ctx.strokeStyle = '#DC143C'
// y轴最高点位置
let yHigh = yAxis.convertToPixel(max)
// y轴最低点位置
let yLow = yAxis.convertToPixel(min)
ctx.beginPath()
// 画笔移动到数据的x轴起始点,y轴最高点
ctx.moveTo(x, yHigh)
if (i === start) { // 如果是第一条数据则需要画一条竖线
ctx.lineTo(x, yLow) // 画竖线
ctx.moveTo(x, yHigh) // 画笔移回
}
if (i === end) { // 如果是最后一条数据则需要画一条竖线
ctx.lineTo(x, yLow) // 画竖线
ctx.fillText(max, x + viewport.dataSpace, yHigh) // 标识箱体最高点的值
ctx.moveTo(x, yLow) // 画笔移动到Y轴最低点
ctx.fillText(min, x + viewport.dataSpace, yLow) // 标识箱体最低点的值
} else { // 画两条横线,一条在y轴最高点,一条在y轴最低点
ctx.lineTo(x + viewport.dataSpace, yHigh) // y轴最高点横线
ctx.moveTo(x, yLow) // 画笔移动到y轴最低点
ctx.lineTo(x + viewport.dataSpace, yLow) // y轴最低点横线
}
ctx.stroke()
ctx.closePath()
// 画黄金线
// 黄金线颜色
ctx.strokeStyle = '#ffffff'
// 根据黄金线参数计算黄金线的值
let goldenSectionLineA = (max - min) * goldenSectionA + min
let goldenSectionLineB = (max - min) * goldenSectionB + min
let goldenSectionLineC = (max - min) * goldenSectionC + min
let goldenSectionLineD = (max - min) * goldenSectionD + min
let goldenSectionLineE = (max - min) * goldenSectionE + min
// 根据黄金线的值获取Y轴高度
let yA = yAxis.convertToPixel(goldenSectionLineA)
let yB = yAxis.convertToPixel(goldenSectionLineB)
let yC = yAxis.convertToPixel(goldenSectionLineC)
let yD = yAxis.convertToPixel(goldenSectionLineD)
let yE = yAxis.convertToPixel(goldenSectionLineE)
ctx.beginPath()
// 画第一条黄金线
ctx.moveTo(x, yA)
ctx.lineTo(x + viewport.barSpace / 2, yA)
if (i === end) { // 是否最后一条数据,如果是则需要标识黄金线的值
// 标识第一条黄金线的值
ctx.fillText(goldenSectionLineA.toFixed(2), x + viewport.dataSpace, yA)
}
// 画第二条黄金线
ctx.moveTo(x, yB)
ctx.lineTo(x + viewport.barSpace / 2, yB)
if (i === end) {
ctx.fillText(goldenSectionLineB.toFixed(2), x + viewport.dataSpace, yB)
}
// 画第三条黄金线
ctx.moveTo(x, yC)
ctx.lineTo(x + viewport.barSpace / 2, yC)
if (i === end) {
ctx.fillText(goldenSectionLineC.toFixed(2), x + viewport.dataSpace, yC)
}
// 画第四条黄金线
ctx.moveTo(x, yD)
ctx.lineTo(x + viewport.barSpace / 2, yD)
if (i === end) {
ctx.fillText(goldenSectionLineD.toFixed(2), x + viewport.dataSpace, yD)
}
// 画第五条黄金线
ctx.moveTo(x, yE)
ctx.lineTo(x + viewport.barSpace / 2, yE)
if (i === end) {
ctx.fillText(goldenSectionLineE.toFixed(2), x + viewport.dataSpace, yE)
}
ctx.stroke()
ctx.closePath()
}
// 计算X轴的下一个位置
x += viewport.dataSpace
})
}
})

# 结语

以上代码只是箱体的指标模版,还需要根据业务逻辑在标记上实现点击事件,然后通过事件动态添加移除箱体指标。