自定义组件之绘制折线图和曲线图-鸿蒙开发者社区-51CTO.COM

自定义组件之绘制折线图和曲线图

自定义组件之绘制折线图和曲线图

HarmonyOS
2024-05-26 16:12:07
浏览
收藏 0
回答 1
待解决
回答 1
按赞同
/
按时间
pumayze

本文自定义组件的主要的功能有折线图,曲线图,区域渐变覆盖等,如果需要更多的功能,也可以在此基础上进行扩展。

使用的核心API

核心代码解释

首先先描述下绘制步骤:

1. 先绘制坐标系,然后绘制坐标系上的点,再绘制坐标系上文字。

2. 然后绘制曲线图和折线图

3. 最后绘制区域覆盖的地方

4. 加入绘画效果    绘制前肯定要初始化各种画笔,以及设置控件的宽高等。首先初始化画笔

  //绘制坐标系的画笔 
  private mPaintCdt:Paint = new Paint(); 
  //绘制坐标系上刻度点的画笔 
  private mPaintSysPoint:Paint = new Paint(); 
  //绘制折线上的点的画笔 
  private mPaintLinePoint:Paint = new Paint(); 
  //绘制文字的画笔 
  private mPaintText:Paint = new Paint(); 
  //绘制折线的画笔 
  private mPaintLine:Paint = new Paint(); 
  //绘制虚线的画笔 
  private mPaintDash:Paint = new Paint(); 
  //x,y轴的画笔 
  private mPaintSys:Paint = new Paint(); 
  //绘制覆盖区域 
  private mPaintFillArea:Paint = new Paint(); 
//初始化画笔 
  initPaint() { 
    //绘制坐标系的画笔 
    this.mPaintCdt.setStrokeWidth(this.brokenLineSize); 
    this.mPaintCdt.setColor(this.brokenLineColor); 
  
    //绘制远点颜色 
    this.mPaintLinePoint.setColor(this.brokenLineColor); 
  
    //绘制坐标系上刻度点的画笔 
    this.mPaintSysPoint.setColor(this.coordinateSystemColor) 
  
    //x轴与y轴绘制画笔 
    this.mPaintSys.setStrokeWidth(this.coordinateSystemSize) 
    this.mPaintSys.setColor(this.coordinateSystemColor) 
  
    this.mPaintText.textAlign = 'center'; 
    this.mPaintText.strokeStyle = Color.White; 
    this.mPaintText.font = '40px sans-serif' 
  
    this.mPaintLine.lineWidth = this.brokenLineSize; 
    this.mPaintLine.strokeStyle = this.brokenLineColor; 
  
    //绘制虚线的画笔 
    this.mPaintDash.lineWidth = this.dashSize; 
    this.mPaintDash.strokeStyle = this.dashColor; 
  
    //填充区域颜色 
    this.mPaintFillArea.strokeStyle = Color.Yellow; 
    this.mPaintFillArea.lineWidth = this.brokenLineSize; 
  }

然后设置宽高,这里我直接将绘制组件设置高度一半

Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center, justifyContent: FlexAlign.Center }) { 
      Canvas(this.context) 
        .width('100%') 
        .height('40%') 
        .margin({top:10}) 
        .backgroundColor(Color.Blue) 
        .onReady(() => { 
          this.onDraw(); 
        }) 
    } 
    .width('100%') 
    .height('100%')

现在就可以开始进行绘制了,绘制之前我们要根据设置的数据来进行绘制,所以要提供设置数据的方法

  setCharPoints(points:List<number>) { 
    this.pointList = new List(); 
    points.forEach((value,index)=> { 
      let point = new Point(); 
      point.x = index; 
      point.y = value; 
      this.pointList.add(point); 
    }) 
  
    //x轴传入的最大坐标 
    this.maxX = this.pointList.length; 
    //Y轴最大坐标 
    this.maxY = 40; 
  
    console.info('points.length =='+this.maxX) 
    //默认x有6格,y有4格 
    this.xScale = this.maxX /6; 
    this.yScale = this.maxY / 4; 
  }

这样设置数据的操作就好了;接下来还需要在绘制之前需要计算x轴和y轴每段的具体值,通过平分view的宽高来计算代码如下

  //绘制前准备 
  getWidthAndHeight(){ 
    this.mViewHeight = this.context.height; 
    this.mViewWidth = this.context.width; 
    //x轴上需要绘制的刻度个数 
    let numX = this.maxX / this.xScale; 
    //每格的宽度 
    this.everXWidth = (this.mViewWidth - this.marginSize *2) / numX; 
    //y轴上需要绘制的刻度个数 
    let numY = this.maxY / this.yScale; 
    //每隔的高度 
    this.everyYHeight = (this.mViewHeight - this.marginSize * 2) /numY; 
  }

然后就可以进行绘制了,首先绘制坐标系和坐标原点,代码如下:

 //绘制X轴Y周以及圆点 
  drawCoordinate() { 
    //绘制线 
    let mStrokePath:DrawStrokePath = new DrawStrokePath(); 
    mStrokePath.paint = this.mPaintSys; 
    mStrokePath.path = new Path2D(); 
    //绘制远点 
    mStrokePath.path.moveTo(this.marginSize,this.mViewHeight-this.marginSize) 
    mStrokePath.path.lineTo(this.marginSize,5); 
  
    mStrokePath.path.moveTo(this.marginSize,this.mViewHeight-this.marginSize) 
    mStrokePath.path.lineTo(this.mViewWidth - 5, this.mViewHeight - this.marginSize); 
    mStrokePath.draw(this.context) 
  
    //绘制原点 
    let mCircleFillPath:DrawFillPath = new DrawFillPath(); 
    mCircleFillPath.paint = this.mPaintSysPoint; 
    mCircleFillPath.path = new Path2D(); 
    mCircleFillPath.path.arc(this.marginSize, this.mViewHeight-this.marginSize, this.SystemPointRadius, 0, 6.28) 
    mCircleFillPath.draw(this.context) 
  }

效果图如下:

然后绘制坐标系上x,y轴刻度标记和小圆点:

 //然后绘制坐标系上x,y轴刻度标记和小圆点 
  drawCalibration() { 
    //x轴上的点 
    let mCircleFillPath:DrawFillPath = new DrawFillPath(); 
    let mDrawTextPath:DrawTextPath = new DrawTextPath(); 
    mDrawTextPath.paint = this.mPaintText; 
    mCircleFillPath.paint = this.mPaintSysPoint; 
    console.info('everXWidth ==' + this.everXWidth) 
    //绘制X轴的点 
    for (let index = 0; index < this.pointList.length; index++) { 
      mCircleFillPath.path = new Path2D(); 
      mCircleFillPath.path.arc(this.marginSize +index*this.everXWidth, this.mViewHeight-this.marginSize, this.SystemPointRadius, 0, 6.28) 
      mCircleFillPath.draw(this.context); 
      //绘制地下文本 
      mDrawTextPath.point = new Point(); 
      mDrawTextPath.text = this.mXaxisList.get(index-1); 
      mDrawTextPath.point.x = this.marginSize +index*this.everXWidth; 
      mDrawTextPath.point.y = this.mViewHeight-this.marginSize+this.textMarginSize; 
      mDrawTextPath.draw(this.context); 
    } 
  
    //绘制Y轴的点 
    for (let index = 0; index < this.mYaxisList.length; index++) { 
      mCircleFillPath.path = new Path2D(); 
      mCircleFillPath.path.arc(this.marginSize, this.mViewHeight-this.marginSize -  index * this.everyYHeight, this.SystemPointRadius, 0, 6.28) 
      mCircleFillPath.draw(this.context) 
  
      mDrawTextPath.point = new Point(); 
      mDrawTextPath.text = this.mYaxisList.get(index).toString(); 
      mDrawTextPath.point.x = this.marginSize - this.textMarginSize +4; 
      mDrawTextPath.point.y = this.mViewHeight-this.marginSize - index * this.everyYHeight; 
      mDrawTextPath.draw(this.context); 
    } 
  }

效果如下:

之后就开始绘制折线图和曲线图。

  /** 
   * 绘制折线 
   * 计算绘制点的坐标位置 
   * 绘制点的坐标 =  (传入点的的最大的x,y坐标/坐标轴上的间隔) * 坐标间隔对应的屏幕上的间隔 
   */ 
  drawBrokenLine() { 
    //x轴表示月份,数据统计从一月份开始,0月份数据为零 
    let zeroPoint = new Point(); 
    zeroPoint.x = this.marginSize + this.pointList[0].x/this.xScale * this.everXWidth; 
    zeroPoint.y =  this.mViewHeight-this.marginSize - this.pointList[0].y / this.yScale * this.everyYHeight; 
    //绘制线 
    let mStrokePath:DrawStrokePath = new DrawStrokePath(); 
    mStrokePath.paint = this.mPaintLine; 
    mStrokePath.path = new Path2D(); 
    mStrokePath.path.moveTo(zeroPoint.x,zeroPoint.y) 
    let mCircleFillPath:DrawFillPath = new DrawFillPath(); 
    mCircleFillPath.paint = this.mPaintLinePoint 
  
    for (let index = 1; index < this.mAnimatorValue; index++) { 
      var pointX = this.pointList[index].x * this.everXWidth; 
      var pointY = this.pointList[index].y /this.yScale * this.everyYHeight; 
      console.info('LineCharView pointX ==' + pointX + "and pointY ==" + pointY + "x:=="+this.pointList[index].x + "y: ==" + this.pointList[index].y) 
      let endx = this.marginSize + pointX; 
      let endy = this.mViewHeight - this.marginSize - pointY; 
      mStrokePath.path.lineTo(endx,endy); 
      //绘制圆点 
      mCircleFillPath.path = new Path2D(); 
      mCircleFillPath.path.arc(endx, endy, this.SystemPointRadius, 0, 6.28) 
      mCircleFillPath.draw(this.context) 
    } 
    mStrokePath.draw(this.context) 
  }

效果图如下:

绘制曲线这里我是采用的三阶贝赛尔曲线进行绘制的,先取前后两个点的x的中点,设置给两个额外点的x值,两个额外点的y值分别设置为两个点的y值。

 /** 
   * 曲线我是采用的三阶贝赛尔曲线进行绘制的,先取前后两个点的x的中点,设置给两个额外点的x值,两个额外点的y值分别设置为两个点的y值 
   */ 
  drawCurve() { 
    let zeroPoint = new Point(); 
    zeroPoint.x = this.marginSize + this.pointList[0].x/this.xScale * this.everXWidth; 
    zeroPoint.y =  this.mViewHeight-this.marginSize - this.pointList[0].y / this.yScale * this.everyYHeight; 
    //绘制线 
    let mStrokePath:DrawStrokePath = new DrawStrokePath(); 
    mStrokePath.paint = this.mPaintLine; 
    mStrokePath.path = new Path2D(); 
    mStrokePath.path.moveTo(zeroPoint.x,zeroPoint.y) 
    let mDrawFillPath:DrawFillPath = new DrawFillPath(); 
    mDrawFillPath.paint = this.mPaintFillArea; 
    mDrawFillPath.path = new Path2D(); 
    let mCircleFillPath:DrawFillPath = new DrawFillPath(); 
    mCircleFillPath.paint = this.mPaintLinePoint 
  
    if(this.fillArea) { 
      mDrawFillPath.path.moveTo(this.marginSize, this.mViewHeight - this.marginSize) 
      mDrawFillPath.path.lineTo(zeroPoint.x, zeroPoint.y) 
    } 
    for (let index = 0; index < this.mAnimatorValue -1; index++) { 
      var startX = this.pointList[index].x * this.everXWidth; 
      var startY = this.pointList[index].y /this.yScale * this.everyYHeight; 
      let startPx = this.marginSize + startX; 
      let startPy = this.mViewHeight - this.marginSize - startY; 
      var endX = this.pointList[index+1].x * this.everXWidth; 
      var endY = this.pointList[index + 1].y /this.yScale * this.everyYHeight; 
      let endPx = this.marginSize + endX; 
      let endPy = this.mViewHeight - this.marginSize - endY; 
      let wt = (startPx+ endPx) / 2; 
      mStrokePath.path.bezierCurveTo(wt,startPy,wt,endPy,endPx, endPy); 
      //是否绘制填充区域 
      if(this.fillArea) { 
        this.context.fillStyle = Color.Yellow 
        mDrawFillPath.path.lineTo(startPx, startPy) 
        mDrawFillPath.path.bezierCurveTo(wt,startPy,wt,endPy,endPx, endPy); 
        mDrawFillPath.path.lineTo(endPx,this.mViewHeight - this.marginSize); 
        if(index == this.mAnimatorValue -2 ) { 
          mDrawFillPath.path.closePath(); 
        } 
        console.info('LineCharView this.mAnimatorValue ==' + this.mAnimatorValue); 
      } 
      mDrawFillPath.draw(this.context) 
  
      //绘制圆点 
      mCircleFillPath.path = new Path2D(); 
      mCircleFillPath.path.arc(endPx, endPy, this.SystemPointRadius, 0, 6.28) 
      mCircleFillPath.draw(this.context) 
    } 
    mStrokePath.draw(this.context) 
  }

效果图如下:

到这里曲线图与折线就绘制好,最好给图形添加个动画效果,代码如下:

 private initAnimator(maxX: number) { 
    let options:AnimatorOptions = { 
      duration: 1500, 
      easing: "friction", 
      delay: 0, 
      fill: "forwards", 
      direction: "normal", 
      iterations: 2, 
      begin: 0, 
      end:maxX 
    }; 
    this.valueAnimator = Animator.create(options) 
    this.valueAnimator.onframe = (value:number)=> { 
      this.mAnimatorValue = value; 
    } 
    this.valueAnimator.play(); 
  }
分享
微博
QQ
微信
回复
2024-05-27 21:38:02
相关问题
DevEco Stduio如何绘制折线图
4266浏览 • 1回复 待解决
HarmonyOS 能否提供折线图组件
290浏览 • 1回复 待解决
HarmonyOS 自定义组件问题
272浏览 • 1回复 待解决
自定义组件嵌套子组件
9350浏览 • 3回复 待解决
自定义组件onMeasureSize的使用
284浏览 • 1回复 待解决
自定义组件如何导出、引入?
2218浏览 • 1回复 待解决
鸿蒙组件toast自定义样式
8675浏览 • 1回复 待解决
HarmonyOS ArkUI加载自定义组件
325浏览 • 1回复 待解决
如何自定义组件原型菜单
798浏览 • 1回复 待解决
如何自定义模拟Tabs组件
814浏览 • 1回复 待解决
ArkTs如何自定义容器组件
2977浏览 • 1回复 待解决
HarmonyOS 自定义组件的事件处理
296浏览 • 1回复 待解决
HarmonyOS UI组件自定义点击范围
230浏览 • 1回复 待解决
HarmonyOS 键盘顶部添加自定义组件
223浏览 • 1回复 待解决
组件自定义回调函数实现
286浏览 • 1回复 待解决
HarmonyOS 自定义弹框组件问题
335浏览 • 1回复 待解决
自定义组件是否支持renderFit属性
1742浏览 • 1回复 待解决