美文网首页Harmony...
【HarmonyOS NEXT】ArkUI Canvas饼图(P

【HarmonyOS NEXT】ArkUI Canvas饼图(P

作者: 醜捌怪 | 来源:发表于2024-03-31 17:10 被阅读0次

效果图:


piecharts_demo.gif

代码实现

demo传送门

使用

build() {
    Navigation() {
      Column() {
        PieCharts({
          options: this.options
        })

        Row() {
          Button('支出')
            .setButtonStyle(this.billType == 0)
            .onClick(() => {
              this.billType = 0
              this.loadData()
            })
          Blank()
            .width(12)
          Button('收入')
            .setButtonStyle(this.billType != 0)
            .onClick(() => {
              this.billType = 1
              this.loadData()
            })
        }
      }
      .size({ width: '100%', height: "50%" })
    }
    .title('收支分析')
    .titleMode(NavigationTitleMode.Mini)
  }

定义options

options.labelFn设置每个区块的文字;options.labelStyleFn设置文字样式,字体大小和字体颜色;options.colorFn设置每个区块的颜色。

let options = new PieChartsOptions()
options.animate = true
options.duration = 300
options.data = data
options.radius = 60
options.innerRadius = 30
options.labelFn = (data: PieChartsData) => {
  return (data as BillModel).category
}
options.labelStyleFn = (data: PieChartsData, index: number) => {
  let colors = [Color.Green, Color.Blue, Color.Orange, Color.Grey, '#FFCC00', Color.Pink, '#800080', '#ff6633', '#ffcc66', Color.Red]
  let style = new PieChartsLabelStyle()
  style.fontColor = colors[index]
  style.fontSize = 12
  return style
}
options.colorFn = (data: PieChartsData, index: number) => {
  let colors = [Color.Green, Color.Blue, Color.Orange, Color.Grey, '#FFCC00', Color.Pink, '#800080', '#ff6633', '#ffcc66', Color.Red]
  return colors[index]
}

计算每个扇形的角度

const radius = this.options.radius
const innerRadius = this.options.innerRadius
const brokenLineLength = 15
const brokenLineWidth = 1.5
const arcWidth = radius - innerRadius
const arcRadius = innerRadius + arcWidth / 2

let centerX = this.context.width / 2
let centerY = this.context.height / 2

const totalValue = data.reduce((acc, item) => acc + item.getValue(), 0)
const percentages: number[] = data.map(item => item.getValue() / totalValue);

let startAngle = -Math.PI / 2
for (let i = 0; i < percentages.length; i++) {
  let percent = percentages[i]
  let angle = percent * 2 * Math.PI * progress
  let endAngle = startAngle + angle

  // 画扇形
  ...
  // 画折线
  ...
  // 画文字
  ...
  startAngle = endAngle
}

画扇形

this.context.beginPath()
this.context.arc(centerX, centerY, arcRadius, startAngle, endAngle)
this.context.lineWidth = arcWidth
this.context.strokeStyle = color
this.context.stroke()
this.context.restore()

画折线

let centerAngle = startAngle + angle / 2
let r = radius + brokenLineLength / 2

let x1 = centerX + (r - brokenLineLength) * Math.cos(centerAngle)
let y1 = centerY + (r - brokenLineLength) * Math.sin(centerAngle)

let x2 = centerX + r * Math.cos(centerAngle)
let y2 = centerY + r * Math.sin(centerAngle)

let x3 = x2
let y3 = y2
if (centerAngle < Math.PI / 2) {
  this.context.textAlign = 'right'
  x3 = x2 + 15
} else {
  this.context.textAlign = 'left'
  x3 = x2 - 15
}

// 折线
let leaderLineColor = this.options.leaderLineColorFn(item, i)
this.context.beginPath()
this.context.lineWidth = brokenLineWidth
this.context.strokeStyle = leaderLineColor
this.context.moveTo(x1, y1)
this.context.lineTo(x2, y2)
this.context.lineTo(x3, y3)
this.context.stroke()

画文字

// 设置字体样式
const labelStyle = this.options.labelStyleFn(item, i)
this.context.textBaseline = 'middle'
this.context.fillStyle = labelStyle.fontColor
this.context.font = fp2px(labelStyle.fontSize) + 'px sans-serif'
// 获取文本
let label = this.options.labelFn(data[i], i)
let textWidth = this.context.measureText(label).width

let x4 = x3
let y4 = y3
if (centerAngle < Math.PI / 2) {
  this.context.textAlign = 'right'
  x3 = x2 + 15
  x4 = x3 + textWidth + 3
} else {
  this.context.textAlign = 'left'
  x3 = x2 - 15
  x4 = x3 - textWidth - 3
}

this.context.fillText(label, x4, y4)
this.context.stroke()

完整代码

export declare interface PieChartsData {
  getValue(): number
}

export class PieChartsLabelStyle {
  fontSize: number = 14
  fontColor: string | Color = Color.Orange
}

@Observed
export class PieChartsOptions {
  radius: number = 60
  innerRadius: number = 0
  data: PieChartsData[] = []
  animate: boolean = true
  duration: number = 500
  labelFn: (data: PieChartsData, index: number) => string = () => {
    return 'unknown'
  }
  labelStyleFn: (data: PieChartsData, index: number) => PieChartsLabelStyle = () => {
    return new PieChartsLabelStyle()
  }
  colorFn: (data: PieChartsData, index: number) => string | Color = (data: PieChartsData, index: number) => {
    let colors = [Color.Red, Color.Green, Color.Blue, Color.Orange, Color.Grey, Color.Pink, '#FFCC00', '#800080', '#ff6633', '#ffcc66']
    return colors[index % colors.length]
  }
  leaderLineColorFn: (data: PieChartsData, index: number) => string | Color = (data: PieChartsData, index: number) => {
    return Color.Orange
  }
}

@Component
export struct PieCharts {
  @Watch('animateDraw') @Prop options: PieChartsOptions
  private settings: RenderingContextSettings = new RenderingContextSettings(true)
  private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings)

  animateDraw() {
    console.log(`animateDraw`)
    if (!this.options.animate) {
      this.draw(1)
      return
    }

    let count = 0
    let interval = setInterval(() => {
      count++
      if (count == 60) {
        clearInterval(interval)
      }
      this.draw(count / 60)
    }, this.options.duration / 60)
  }

  draw(progress: number) {
    this.clearCanvas()

    const data = this.options.data
    const radius = this.options.radius
    const innerRadius = this.options.innerRadius
    const brokenLineLength = 15
    const brokenLineWidth = 1.5
    const arcWidth = radius - innerRadius
    const arcRadius = innerRadius + arcWidth / 2

    let centerX = this.context.width / 2
    let centerY = this.context.height / 2

    const totalValue = data.reduce((acc, item) => acc + item.getValue(), 0)
    const percentages: number[] = data.map(item => item.getValue() / totalValue);

    let startAngle = -Math.PI / 2
    for (let i = 0; i < percentages.length; i++) {
      let percent = percentages[i]
      let angle = percent * 2 * Math.PI * progress
      let endAngle = startAngle + angle

      let item = data[i]
      let color = this.options.colorFn(item, i)

      // 画扇形
      this.context.beginPath()
      this.context.arc(centerX, centerY, arcRadius, startAngle, endAngle)
      this.context.lineWidth = arcWidth
      this.context.strokeStyle = color
      this.context.stroke()
      this.context.restore()

      // 角度小于Math.PI / 12,不显示label
      if (angle <= Math.PI / 12) {
        startAngle = endAngle
        continue
      }

      // 画折线
      let centerAngle = startAngle + angle / 2
      let r = radius + brokenLineLength / 2

      let x1 = centerX + (r - brokenLineLength) * Math.cos(centerAngle)
      let y1 = centerY + (r - brokenLineLength) * Math.sin(centerAngle)

      let x2 = centerX + r * Math.cos(centerAngle)
      let y2 = centerY + r * Math.sin(centerAngle)

      let x3 = x2
      let y3 = y2
      if (centerAngle < Math.PI / 2) {
        this.context.textAlign = 'right'
        x3 = x2 + 15
      } else {
        this.context.textAlign = 'left'
        x3 = x2 - 15
      }

      // 折线
      let leaderLineColor = this.options.leaderLineColorFn(item, i)
      this.context.beginPath()
      this.context.lineWidth = brokenLineWidth
      this.context.strokeStyle = leaderLineColor
      this.context.moveTo(x1, y1)
      this.context.lineTo(x2, y2)
      this.context.lineTo(x3, y3)
      this.context.stroke()

      // 画文字
      // 设置字体样式
      const labelStyle = this.options.labelStyleFn(item, i)
      this.context.textBaseline = 'middle'
      this.context.fillStyle = labelStyle.fontColor
      this.context.font = fp2px(labelStyle.fontSize) + 'px sans-serif'
      // 获取文本
      let label = this.options.labelFn(data[i], i)
      let textWidth = this.context.measureText(label).width

      let x4 = x3
      let y4 = y3
      if (centerAngle < Math.PI / 2) {
        this.context.textAlign = 'right'
        x3 = x2 + 15
        x4 = x3 + textWidth + 3
      } else {
        this.context.textAlign = 'left'
        x3 = x2 - 15
        x4 = x3 - textWidth - 3
      }

      this.context.fillText(label, x4, y4)
      this.context.stroke()

      startAngle = endAngle
    }
  }

  clearCanvas() {
    this.context.restore();
    this.context.resetTransform();
    this.context.clearRect(0, 0, this.context.width, this.context.height);
  }

  build() {
    Column() {
      Canvas(this.context)
        .width('100%')
        .height('100%')
        .backgroundColor(Color.White)
        .onReady(() => {
          this.animateDraw()
        })
    }
    .size({ width: '100%', height: "50%" })
  }
}

相关文章

  • canvas饼图

  • Canvas 饼图与动效

    2016-06-24 用 Canvas 绘制一个饼图且实现动效。涉及:canvas 基本 api, request...

  • SVG绘制环图

    上篇<原生Canvas绘制饼图>介绍了如何使用Canvas来绘制环图,这篇用SVG标签来实现一下。 上面是完整效果...

  • canvas Five 饼图

    前面几节的链接:http://www.jianshu.com/u/ab8f021be9ee需要大家的支持,接着ca...

  • canvas绘制饼图

    使用javaScript的canvas绘制简单的饼图还是比较容易的,实现的效果图如下所示: 其实现原理就是根据扇形...

  • 浅析HTML5的Canvas——案例绘制

    1. Canvas绘制五环 2.Canvas绘制饼状图以及绘制文字 3. Canvas绘制一堆不断变大变小的随机移...

  • canvas图表(3) - 饼图

      原文地址:canvas图表(3) - 饼图  这几天把canvas图表都优化了下,动画效果更加出色了,可以说很...

  • 微信小程序图表插件(wx-charts)

    微信小程序图表插件(wx-charts)基于canvas绘制,体积小巧支持图表类型饼图、线图、柱状图 、区域图等图...

  • canvas-饼状图

  • Android Canvas绘制饼图

    近期学习到Android的Canvas绘制饼图,特此记录下来防止以后忘记。感谢抛物线老师的无私奉献精神。 这是完成...

网友评论

    本文标题:【HarmonyOS NEXT】ArkUI Canvas饼图(P

    本文链接:https://www.haomeiwen.com/subject/sucbtjtx.html