# 引言

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

# 模版代码

// 箱体指标
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
      })
    }
})

# 结语

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