# 引言
使用指定数据方式得到的线条总是不能达到想要的效果,一是阶梯线在价格发生变化的点存在倾斜,二是高低价的线很难完美显示,所以只能自己动手画了。代码仅供参考,如有错误的地方请指正!
# 模版代码
this.chart.addTechnicalIndicatorTemplate({ | |
name: 'BandHighLowInd', | |
shortName: '波段高低价', | |
calcParams: [{ value: 10 }], | |
precision: 2, | |
plots: [ | |
{ key: 'high', title: '高: ', color: (data, options) => { return options.line.colors[0] } }, | |
{ key: 'low', title: '低: ', color: (data, options) => { return options.line.colors[1] } }, | |
{ key: 'highLow', title: '高低: ', color: (data, options) => { return options.line.colors[2] } } | |
], | |
calcTechnicalIndicator: (dataList, { params }) => { | |
let compareKlineSize = params[0] // 取前后 k 的范围 | |
let highs = [] | |
let lows = [] | |
let highLows = [] | |
let findHigh // 高低价当前查找标识 | |
dataList.forEach(function (kLineData, i) { | |
let frontIndex = i - compareKlineSize | |
if (frontIndex <= 0) { | |
frontIndex = 0 | |
} | |
let frontDatas = [] | |
if (frontIndex < i) { | |
frontDatas = dataList.slice(frontIndex, i) | |
} | |
let afterIndex = i + compareKlineSize + 1 | |
let afterDatas = [] | |
if (i < dataList.length - 1) { | |
afterDatas = dataList.slice(i + 1, afterIndex) | |
} | |
if (frontDatas.length === compareKlineSize && afterDatas.length === compareKlineSize) { // 前后 K 线数据必须都找到要比较的条数 | |
let high = null | |
let low = null | |
frontDatas.concat(afterDatas).forEach(function (kLineData, i) { // 找出前后 k 线中的最高和最低价 | |
if (high === null || kLineData.high > high) { | |
high = kLineData.high | |
} | |
if (low === null || kLineData.low < low) { | |
low = kLineData.low | |
} | |
}) | |
if (kLineData.high > high) { // 当前 k 的高大于前后 k 中找出的高,则放入高价集合中 | |
highs.push({ | |
time: kLineData.timestamp, | |
value: kLineData.high, | |
index: i | |
}) | |
if (isNotEmpty(findHigh)) { | |
if (findHigh && highLows[highLows.length - 1].value !== kLineData.high) { | |
highLows.push({ | |
time: kLineData.timestamp, | |
value: kLineData.high, | |
index: i | |
}) | |
findHigh = false | |
} | |
} else { | |
highLows.push({ | |
time: kLineData.timestamp, | |
value: kLineData.high, | |
index: i | |
}) | |
findHigh = false | |
} | |
} | |
if (kLineData.low < low) { // 当前 k 的低小于前后 k 中找出的低,则放入低价集合中 | |
lows.push({ | |
time: kLineData.timestamp, | |
value: kLineData.low, | |
index: i | |
}) | |
if (isNotEmpty(findHigh)) { | |
if (!findHigh && highLows[highLows.length - 1].value !== kLineData.low) { | |
highLows.push({ | |
time: kLineData.timestamp, | |
value: kLineData.low, | |
index: i | |
}) | |
findHigh = true | |
} | |
} else { | |
highLows.push({ | |
time: kLineData.timestamp, | |
value: kLineData.low, | |
index: i | |
}) | |
findHigh = true | |
} | |
} | |
} | |
}) | |
let high, low, highLow | |
return dataList.map((kLineData, i) => { | |
let item = { | |
} | |
highs.forEach((data) => { | |
if (kLineData.timestamp === data.time) { | |
high = data.value | |
item.highOrigin = true | |
} | |
}) | |
if (isNotEmpty(high)) { // 持续先前的高,画出阶梯线 | |
item.timestamp = kLineData.timestamp | |
item.high = high | |
} | |
lows.forEach((data) => { | |
if (kLineData.timestamp === data.time) { | |
low = data.value | |
item.lowOrigin = true | |
} | |
}) | |
if (isNotEmpty(low)) { // 持续先前的低,画出阶梯线 | |
item.timestamp = kLineData.timestamp | |
item.low = low | |
} | |
highLows.forEach((data) => { | |
if (kLineData.timestamp === data.time) { | |
highLow = data.value | |
item.highLowOrigin = true | |
} | |
}) | |
if (isNotEmpty(highLow)) { // 持续先前的高低,方便 title 显示 | |
item.timestamp = kLineData.timestamp | |
item.highLow = highLow | |
} | |
return item | |
}) | |
}, | |
render: ({ ctx, dataSource, viewport, styles, xAxis, yAxis }) => { | |
if (dataSource.technicalIndicatorDataList.length <= 0) { | |
return | |
} | |
let x = xAxis.convertToPixel(0) | |
let high, low, highLow, highLowX | |
dataSource.technicalIndicatorDataList.forEach(function (data, i) { | |
// 画高线 | |
ctx.textBaseline = 'middle' | |
ctx.textAlign = 'center' | |
ctx.fillStyle = '#fff' | |
ctx.strokeStyle = '#fff' | |
if (styles.line && styles.line.colors && styles.line.colors[0]) { | |
ctx.fillStyle = styles.line.colors[0] | |
ctx.strokeStyle = styles.line.colors[0] | |
} | |
if (isNotEmpty(high) && data.high !== high) { | |
ctx.beginPath() | |
ctx.moveTo(x, yAxis.convertToPixel(high)) | |
ctx.lineTo(x, yAxis.convertToPixel(data.high)) | |
ctx.stroke() | |
ctx.closePath() | |
} | |
high = data.high | |
let y = yAxis.convertToPixel(high) | |
ctx.beginPath() | |
ctx.moveTo(x, y) | |
ctx.lineTo(x + viewport.dataSpace, y) | |
ctx.stroke() | |
ctx.closePath() | |
// 画低线 | |
ctx.fillStyle = '#fff' | |
ctx.strokeStyle = '#fff' | |
if (styles.line && styles.line.colors && styles.line.colors[1]) { | |
ctx.fillStyle = styles.line.colors[1] | |
ctx.strokeStyle = styles.line.colors[1] | |
} | |
if (isNotEmpty(low) && data.low !== low) { | |
ctx.beginPath() | |
ctx.moveTo(x, yAxis.convertToPixel(low)) | |
ctx.lineTo(x, yAxis.convertToPixel(data.low)) | |
ctx.stroke() | |
ctx.closePath() | |
} | |
low = data.low | |
y = yAxis.convertToPixel(low) | |
ctx.beginPath() | |
ctx.moveTo(x, y) | |
ctx.lineTo(x + viewport.dataSpace, y) | |
ctx.stroke() | |
ctx.closePath() | |
// 画高低线 | |
if (isNotEmpty(data.highLow) && data.highLowOrigin === true) { | |
if (isNotEmpty(highLow) && isNotEmpty(highLowX)) { | |
ctx.fillStyle = '#fff' | |
ctx.strokeStyle = '#fff' | |
if (styles.line && styles.line.colors && styles.line.colors[2]) { | |
ctx.fillStyle = styles.line.colors[2] | |
ctx.strokeStyle = styles.line.colors[2] | |
} | |
ctx.beginPath() | |
ctx.moveTo(highLowX, yAxis.convertToPixel(highLow)) | |
ctx.lineTo(x, yAxis.convertToPixel(data.highLow)) | |
ctx.stroke() | |
ctx.closePath() | |
} | |
highLow = data.highLow | |
highLowX = x | |
} | |
// 画价格 | |
if (data.high !== undefined && data.highOrigin === true) { // 画高价 | |
let text = Number.parseFloat(data.high).toFixed(2) | |
ctx.fillStyle = '#fff' | |
if (styles.line && styles.line.colors && styles.line.colors[0]) { | |
ctx.fillStyle = styles.line.colors[0] | |
} | |
let offset = 10 | |
let y = yAxis.convertToPixel(data.high) | |
for (let i = 2; i < offset - 1; i += 2) { | |
ctx.fillText('.', x, y - i) | |
} | |
ctx.fillText(text, x, y - offset) | |
} | |
if (data.low !== undefined && data.lowOrigin === true) { // 画低价 | |
let text = Number.parseFloat(data.low).toFixed(2) | |
ctx.fillStyle = '#fff' | |
if (styles.line && styles.line.colors && styles.line.colors[1]) { | |
ctx.fillStyle = styles.line.colors[1] | |
} | |
let offset = 15 | |
let y = yAxis.convertToPixel(data.low) | |
for (let i = 2; i < offset - 5; i += 2) { | |
ctx.fillText('.', x, y + i) | |
} | |
ctx.fillText(text, x, y + offset) | |
} | |
x += viewport.dataSpace | |
}) | |
} | |
}) |