# KLineChart 画波段高低价图 - KLineChart - 工作 | ☆∵∴Hito∴∵★ = 涛声依旧 = 天下事有难易乎？为之，则难者亦易矣
Keywords: KLineChart,K线,自定义指标

#### # 引言

使用指定数据方式得到的线条总是不能达到想要的效果，一是阶梯线在价格发生变化的点存在倾斜，二是高低价的线很难完美显示，所以只能自己动手画了。代码仅供参考，如有错误的地方请指正！

#### # 模版代码

    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

          })

        }

    })

KLineChart K线 自定义指标