鴻蒙開發(fā)折線圖

最近一直在做鴻蒙開發(fā)往扔,正好記錄下疲眷。

interface Point {
  x: number,
  y: number
}

export class PointItem {
  date: string
  value: number

  constructor(date: string, value: number) {
    this.date = date
    this.value = value
  }

  public PointItem(point: PointItem) {
    this.date = point.date
    this.value = point.value
  }
}

@Component
export struct LineChart {
  @State xTicks: String[] = [] // x軸顯示的刻度
  @State yTicks: number[] = [] // y軸顯示的刻度值
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private ctx: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)
  @Prop canvasWidth: number // 畫布的寬度
  private canvasHeight = 300 // 畫布的高度
  private yWidth = 20 // y軸的文字寬度
  private xHeight = 20 // X軸的文字高度
  private points: Point[] = [] // 原始數(shù)量大小
  private sps: any[] = [] // 平滑曲線的數(shù)量
  private grayColor = '#ccc'
  @State minY: number = this.xHeight // 對應(yīng)的是y軸最小值
  @Prop xGridCount: number // x軸網(wǎng)格線的數(shù)量
  @Prop yGridCount: number // y軸網(wǎng)格線的數(shù)量
  private drawInterval: number = -1; // 定時器
  @State startIndex: number = 0 // 動畫出現(xiàn)點
  @State useAnimate: boolean = false // 是否使用動畫
  @State animateCount: number = 2 // 使用動畫時 一個間隔時間內(nèi)繪制的點或者線的數(shù)量
  @State animateTimeGap: number = 100 // 使用動畫時的時間間隔
  @Prop smooth: boolean // 是否使用平滑曲線
  @State logs: string = ""
  private gap = (this.canvasWidth - this.yWidth) / data.length // 兩個點之間的寬度 用來判斷點擊的范圍是否在某個點內(nèi)
  @State scaleRatio: number = 2 // 縮放比例 最小1 最大
  @State lastPoint: number = -1 // 點擊了圖表對應(yīng)的x軸的位置 用于畫垂直虛線
  @Link clickPoint: PointItem
  @State showAera: boolean = true // 是否顯示面積圖
  @State aeraYBase: number = 0 // 面積圖的基準(zhǔn)榆综,默認(rèn)是最小值

  aboutToAppear() {
    let max = Math.ceil(Math.max(...data.map((d => d.value))) / 10) * 10
    let min = Math.floor(Math.min(...data.map((d => d.value))) / 10) * 10
    this.minY = min
    this.aeraYBase = min
    // 從上到下畫 上大下小
    for (let i = this.yGridCount - 1; i >= 0; i--) {
      this.yTicks.push(min + i * ((max - min) / (this.yGridCount - 1)))
    }

    for (let i = 0; i < this.xGridCount - 1; i++) {
      this.xTicks.push(data[Math.round(i * (data.length / (this.xGridCount - 1)))].date)
    }
    this.xTicks.push(data[data.length-1].date);

    let xgap = (this.canvasWidth - this.yWidth) / data.length
    let ratio = 0.1 // 最小代表多少值
    let e = (max - min) / ratio // Y軸 按照值 分多少份
    let ev = (this.canvasHeight - this.xHeight) / e // 每份代表多少多少坐標(biāo)值

    let nullIndexs = []
    for (let i = 0; i < data.length; i++) {
      if (data[i].value === null) {
        nullIndexs.push(i)
      }
      this.points.push({
        x: i * xgap + this.yWidth,
        y: data[i].value === null ? null : (max - data[i].value) / ratio * ev
      })
    }
    if (this.smooth) {
      // 使用平滑曲線 計算平滑曲線的點

      this.sps = bezier.catmullRom2bezier(this.points, false, null)
    }
  }

  updateTime = () => {
    this.ctx.clearRect(0, 0, this.canvasWidth, this.canvasWidth)
    this.drawXLine()
    this.drawYLine()
    this.drawGrid()
  }

  // 畫網(wǎng)格
  drawGrid() {
    this.ctx.save()
    this.ctx.fillStyle = this.grayColor
    let h = (this.canvasHeight - this.xHeight) / (this.yGridCount - 1)
    let w = (this.canvasWidth - this.yWidth) / (this.xGridCount - 1)
    // 橫向
    for (let i = 0; i < this.yGridCount - 1; i++) {
      this.ctx.beginPath()
      this.ctx.fillStyle = colors[i]
      this.ctx.moveTo(this.yWidth, h * i)
      this.ctx.setLineDash([1, 5])
      this.ctx.lineTo(this.canvasWidth, h * i)
      this.ctx.stroke()
    }
    // 縱向
    for (let i = 1; i < this.xGridCount; i++) {
      this.ctx.beginPath()
      this.ctx.fillStyle = colors[i]
      this.ctx.moveTo(w * i + this.yWidth, 0)
      this.ctx.setLineDash([1, 5])
      this.ctx.lineTo(w * i + this.yWidth, this.canvasHeight - this.xHeight)
      this.ctx.stroke()
    }
    this.ctx.restore()
  }

  private path2Db: Path2D = new Path2D()

  // 畫背景面積
  drawAera() {
    this.ctx.save()
    this.ctx.beginPath()
    let height = this.canvasHeight - this.xHeight
    let grad = this.ctx.createLinearGradient(0, 0, 0, height)
    grad.addColorStop(0.0, '#1890FF')
    grad.addColorStop(1.0, '#f7f7f7')
    this.ctx.fillStyle = grad
    let start = 0
    if (this.useAnimate) {
      if (this.points[this.startIndex].y == null) {
        start = this.startIndex + 1
        this.path2Db.moveTo(this.points[start].x, this.points[start].y)
      } else {
        start = this.startIndex
        this.path2Db.moveTo(this.points[this.startIndex].x, this.points[this.startIndex].y)
      }
      let tn = this.startIndex + this.animateCount
      let total = Math.min(this.smooth ? this.sps.length : data.length, tn)
      for (let i = this.startIndex + 1; i < total; i++) {
        let p = this.points[i]
        if (p.y === null) {
          start = i + 1
          this.path2Db.moveTo(this.points[start].x, this.points[start].y)
          continue
        }
        this.path2Db.lineTo(p.x, p.y)
        if (i + 1 < total - 1) {
          if (this.points[i+1].y === null) {
            this.path2Db.lineTo(p.x, height)
            this.path2Db.lineTo(this.points[start].x, height)
            continue
          }
        }
        if (i === total - 1) {
          this.path2Db.lineTo(p.x, height)
          this.path2Db.lineTo(this.points[start].x, height)
        }
      }
    } else {
      this.path2Db.moveTo(this.points[0].x, this.points[0].y)
      for (let i = 1; i < this.points.length; i++) {
        let p = this.points[i]
        if (p.y === null) {
          start = i + 1
          this.path2Db.moveTo(this.points[i+1].x, this.points[i+1].y)
          continue
        }
        this.path2Db.lineTo(p.x, p.y)
        if (i + 1 < this.points.length - 1) {
          if (this.points[i+1].y === null) {
            this.path2Db.lineTo(p.x, height)
            this.path2Db.lineTo(this.points[start].x, height)
            continue
          }
        }
        if (i === this.points.length - 1) {
          this.path2Db.lineTo(p.x, height)
          this.path2Db.lineTo(this.points[start].x, height)
        }
      }
    }
    this.path2Db.closePath()
    this.ctx.fill(this.path2Db)
    this.ctx.restore()
  }

  // 畫點
  drawPoints() {
    let radius = 2
    this.ctx.save()
    this.ctx.fillStyle = colors[0]
    let total = 0
    if (this.useAnimate) {
      let tn = this.startIndex + this.animateCount
      total = Math.min(this.smooth ? this.sps.length : data.length, tn)
    } else {
      total = this.smooth ? this.sps.length : data.length
    }
    for (let i = this.startIndex; i < total; i++) {
      if (this.points[i].y === null) {
        continue
      }
      this.ctx.beginPath()
      this.ctx.arc(this.points[i].x, this.points[i].y, radius, 0, 2 * Math.PI, false)
      this.ctx.fill()
    }

    this.ctx.restore()

  }

  // 畫折線
  drawLine() {
    this.ctx.save()
    this.ctx.beginPath()
    this.ctx.lineWidth = 1
    this.ctx.strokeStyle = colors[0]
    this.ctx.moveTo(this.points[this.startIndex].x, this.points[this.startIndex].y)
    let total = 0
    if (this.useAnimate) {
      let tn = this.startIndex + this.animateCount
      total = Math.min(this.smooth ? this.sps.length : data.length, tn)
    } else {
      total = this.smooth ? this.sps.length : data.length
    }
    if (this.smooth) {
      for (let i = this.startIndex; i < total; i++) {
        if (this.points[i].y === null) {
          ++i
          this.ctx.moveTo(this.points[i].x, this.points[i].y)
        } else {
          let sp = this.sps[i];
          this.ctx.bezierCurveTo(sp[1], sp[2], sp[3], sp[4], sp[5], sp[6])
        }
      }
    } else {
      for (let i = this.startIndex; i < total; i++) {
        if (this.points[i].y === null) {
          i++
          this.ctx.moveTo(this.points[i].x, this.points[i].y)
        } else {
          this.ctx.lineTo(this.points[i].x, this.points[i].y)
        }
      }
    }
    this.ctx.stroke()
    this.ctx.restore()

  }

  // x軸
  drawXLine() {
    this.ctx.save()
    this.ctx.beginPath()
    this.ctx.lineWidth = 1
    this.ctx.strokeStyle = this.grayColor
    this.ctx.moveTo(this.yWidth, this.canvasHeight - this.xHeight)
    this.ctx.lineTo(this.canvasWidth, this.canvasHeight - this.xHeight)
    this.ctx.stroke()

    this.ctx.font = '20px'
    this.ctx.textAlign = "center"
    this.ctx.textBaseline = "middle"

    for (let i = 0; i < this.xTicks.length; i++) {
      const xValue = this.xTicks[i];
      let x = 0
      if (i === 0) {
        this.ctx.textAlign = "start"
        x = this.yWidth
      } else if (i === this.xTicks.length - 1) {
        this.ctx.textAlign = "end"
        x = this.canvasWidth
      } else {
        x = i * (this.canvasWidth - this.yWidth) / (this.xTicks.length - 1)
      }
      this.ctx.fillStyle = '#000'
      this.ctx.fillText(`${xValue}`, x, this.canvasHeight - this.xHeight + 10)
    }
    this.ctx.closePath()
    this.ctx.restore()
  }

  // y軸
  drawYLine() {
    this.ctx.save()
    this.ctx.beginPath()
    this.ctx.lineWidth = 1
    this.ctx.strokeStyle = this.grayColor
    this.ctx.moveTo(this.yWidth, 0)
    this.ctx.lineTo(this.yWidth, this.canvasHeight - this.xHeight)
    this.ctx.stroke()
    this.ctx.font = '20px'
    this.ctx.textAlign = "end"
    this.ctx.textBaseline = "middle"
    let maxWidth = this.ctx.measureText(`${this.yTicks[1]}`).width
    for (let i = 0; i < this.yTicks.length; i++) {
      const yValue = this.yTicks[i];
      let y = 0
      if (i === 0) {
        y = 5
      } else if (i === this.yGridCount) {
        y = this.canvasWidth - this.xHeight
      } else {
        y = i * (this.canvasHeight - this.xHeight) / (this.yGridCount - 1)
      }
      this.ctx.fillStyle = '#000'
      this.ctx.fillText(`${yValue}`, maxWidth, y)
    }
    this.ctx.restore()
  }

  // 繪制點擊時數(shù)據(jù)的提示
  drawToolTip(x: number) {
    if (this.lastPoint !== -1) {
      this.ctx.clearRect(this.lastPoint - 0.5, 0, 1, this.canvasHeight - this.xHeight)
    }
    this.ctx.save()
    this.ctx.beginPath()
    this.ctx.fillStyle = this.grayColor

    // this.ctx.fillRect(0, 0, 100, 20)
    this.ctx.lineWidth = 1
    this.ctx.moveTo(x, 0)
    this.ctx.setLineDash([1, 5])
    this.ctx.lineTo(x, this.canvasHeight - this.xHeight)
    this.ctx.stroke()
    this.ctx.restore()
    this.lastPoint = x

  }

  build() {
    Column() {
      Text(this.logs)
      // Scroll() {
      Stack({ alignContent: Alignment.Center }) {
        Canvas(this.ctx)
          .id("canvas")
          .width(this.canvasWidth)
          .height(this.canvasHeight)
          .onReady(() => {
            this.updateTime()

            if (this.useAnimate) {
              this.drawInterval = setInterval(() => {
                this.drawLine()
                if (this.showAera) {
                  this.drawAera()
                }
                this.drawPoints()

                if (this.useAnimate) {
                  this.startIndex = this.startIndex + this.animateCount - 1
                }
                if (this.drawInterval !== -1 && this.startIndex >= data.length) {
                  clearInterval(this.drawInterval)
                  return
                }
              }, this.animateTimeGap)
            } else {
              this.drawLine()
              if (this.showAera) {
                this.drawAera()
              }
              this.drawPoints()
            }
          })
          .onTouch((e: TouchEvent) => {
            if (e.type == TouchType.Down || e.type == TouchType.Move) {
              this.getCurrentPointData([e.touches[0].x, e.touches[0].screenX])
            }
          })
          .backgroundColor(Color.Gray)
        if (this.lastPoint !== -1) {
          Line()
            .startPoint([this.lastPoint, 0])
            .endPoint([this.lastPoint, this.canvasHeight - this.xHeight])
            .stroke(this.grayColor)
            .strokeWidth(1)
            .strokeDashArray([10, 3])
            .strokeLineCap(LineCapStyle.Round)
        }
      }

      // .backgroundColor(Color.Gray)
      // }
      // .layoutWeight(1)
    }

  }

  aboutToDisappear() {
    if (this.drawInterval !== -1) {
      clearInterval(this.drawInterval)
    }
  }

  getCurrentPointData(e: [number, number]) {
    let index = -1
    let distance = -1
    for (let i = 0; i < this.points.length; i++) {
      const element = this.points[i];
      let dis = bezier.distance([e[0], 0], [element.x, 0])
      if (dis <= this.gap) {
        if (index === -1 || dis < distance) {
          distance = dis
          index = i
        }
      }
    }
    if (index !== -1) {
      this.lastPoint = e[1] // 按照屏幕中的位置畫線
    }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末糠爬,一起剝皮案震驚了整個濱河市楣号,隨后出現(xiàn)的幾起案子叼耙,更是在濱河造成了極大的恐慌腕窥,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件筛婉,死亡現(xiàn)場離奇詭異簇爆,居然都是意外死亡,警方通過查閱死者的電腦和手機倾贰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門冕碟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人匆浙,你說我怎么就攤上這事安寺。” “怎么了首尼?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵挑庶,是天一觀的道長。 經(jīng)常有香客問我软能,道長迎捺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任查排,我火速辦了婚禮凳枝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘跋核。我一直安慰自己岖瑰,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布砂代。 她就那樣靜靜地躺著蹋订,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刻伊。 梳的紋絲不亂的頭發(fā)上露戒,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天椒功,我揣著相機與錄音,去河邊找鬼智什。 笑死动漾,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的撩鹿。 我是一名探鬼主播谦炬,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼悦屏,長吁一口氣:“原來是場噩夢啊……” “哼节沦!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起础爬,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤甫贯,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后看蚜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叫搁,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年供炎,在試婚紗的時候發(fā)現(xiàn)自己被綠了渴逻。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡音诫,死狀恐怖惨奕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情竭钝,我是刑警寧澤梨撞,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站香罐,受9級特大地震影響卧波,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庇茫,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一港粱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧旦签,春花似錦查坪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至淋淀,卻和暖如春遥昧,著一層夾襖步出監(jiān)牢的瞬間覆醇,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工炭臭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留永脓,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓鞋仍,卻偏偏與公主長得像常摧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子威创,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344