HarmonyOS-MPChart绘制一条虚实相接的曲线

本文是基于鸿蒙三方库mpchart(OpenHarmony-SIG/ohos-MPChart)的使用,自定义绘制方法,绘制一条虚实相接的曲线。

mpchart本身的绘制功能是不支持虚实相接的曲线的,要么完全是实线,要么完全是虚线。那么当我们的需求是一半是虚线,一半是实线的曲线时,就需要自己定义方法进行绘制了。

首先,我们需要写一个MyLineDataSet类,继承自LineDataSet,也就是线型图的数据类。为什么需要这个类呢?因为我们需要在初始化数据的时候定义这个虚实相接的线是从哪里开始由实线变为虚线的,这里MyLineDataSet类的构造方法比它的父类多了一个interval参数,也就是虚实分隔点。

TypeScript 复制代码
import { EntryOhos, JArrayList, LineDataSet } from '@ohos/mpchart';

export class MyLineDataSet extends LineDataSet {
  interval: number = 0;
  constructor(yVals: JArrayList<EntryOhos> | null, label: string, interval: number) {
    super(yVals, label);
    this.interval = interval;
  }
}

定义好自己的数据类之后,就要定义MyRender类了,实线具体的绘制功能,MyRender类继承自LineChartRenderer,因为是要绘制曲线,所以重写的是drawCubicBezier方法,MyRender类的代码如下:

TypeScript 复制代码
import { EntryOhos, ILineDataSet, Style, Transformer, Utils, LineChartRenderer } from '@ohos/mpchart';
import { MyLineDataSet } from './MyLineDataSet';

export default class MyRender extends LineChartRenderer{
  protected drawCubicBezier(c: CanvasRenderingContext2D, dataSet: MyLineDataSet) {
    if(dataSet.interval == undefined){
      super.drawCubicBezier(c, dataSet);
      return;
    }
    if (!this.mChart || !this.mXBounds) {
      return;
    }
    const phaseY: number = this.mAnimator ? this.mAnimator.getPhaseY() : 1;
    const trans: Transformer | null = this.mChart.getTransformer(dataSet.getAxisDependency());

    this.mXBounds.set(this.mChart, dataSet);

    const intensity: number = dataSet.getCubicIntensity();

    let cubicPath = new Path2D();
    //实线
    let solidLinePath = new Path2D();
    //虚线
    let dashedLinePath = new Path2D();
    if (this.mXBounds.range >= 1) {
      let prevDx: number = 0;
      let prevDy: number = 0;
      let curDx: number = 0;
      let curDy: number = 0;

      // Take an extra point from the left, and an extra from the right.
      // That's because we need 4 points for a cubic bezier (cubic=4), otherwise we get lines moving and doing weird stuff on the edges of the chart.
      // So in the starting `prev` and `cur`, go -2, -1
      // And in the `lastIndex`, add +1

      const firstIndex: number = this.mXBounds.min + 1;
      const lastIndex: number = this.mXBounds.min + this.mXBounds.range;

      let prevPrev: EntryOhos | null;
      let prev: EntryOhos | null = dataSet.getEntryForIndex(Math.max(firstIndex - 2, 0));
      let cur: EntryOhos | null = dataSet.getEntryForIndex(Math.max(firstIndex - 1, 0));
      let next: EntryOhos | null = cur;
      let nextIndex: number = -1;

      if (cur === null) return;

      Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
      // let the spline start
      cubicPath.moveTo(cur.getX(), cur.getY() * phaseY);
      solidLinePath.moveTo(cur.getX(), cur.getY() * phaseY);

      for (let j: number = this.mXBounds.min + 1; j <= this.mXBounds.range + this.mXBounds.min; j++) {
        prevPrev = prev;
        prev = cur;
        cur = nextIndex === j ? next : dataSet.getEntryForIndex(j);

        nextIndex = j + 1 < dataSet.getEntryCount() ? j + 1 : j;
        next = dataSet.getEntryForIndex(nextIndex);

        prevDx = (cur.getX() - prevPrev.getX()) * intensity;
        prevDy = (cur.getY() - prevPrev.getY()) * intensity;
        curDx = (next.getX() - prev.getX()) * intensity;
        curDy = (next.getY() - prev.getY()) * intensity;

        cubicPath.bezierCurveTo(
          prev.getX() + prevDx,
          (prev.getY() + prevDy) * phaseY,
          cur.getX() - curDx,
          (cur.getY() - curDy) * phaseY,
          cur.getX(),
          cur.getY() * phaseY
        );
        if(j <= dataSet.interval){
          solidLinePath.bezierCurveTo(
            prev.getX() + prevDx,
            (prev.getY() + prevDy) * phaseY,
            cur.getX() - curDx,
            (cur.getY() - curDy) * phaseY,
            cur.getX(),
            cur.getY() * phaseY
          );
          if(j == dataSet.interval) {
            dashedLinePath.moveTo(cur.getX(),
              cur.getY() * phaseY);
          }
        }else{
          dashedLinePath.bezierCurveTo(
            prev.getX() + prevDx,
            (prev.getY() + prevDy) * phaseY,
            cur.getX() - curDx,
            (cur.getY() - curDy) * phaseY,
            cur.getX(),
            cur.getY() * phaseY
          );
        }
      }
    }

    // if filled is enabled, close the path
    if (dataSet.isDrawFilledEnabled()) {
      let cubicFillPath: Path2D = new Path2D();
      // cubicFillPath.reset();
      cubicFillPath.addPath(cubicPath);

      if (c && trans) {
        this.drawCubicFill(c, dataSet, cubicFillPath, trans, this.mXBounds);
      }
    }

    this.mRenderPaint.setColor(dataSet.getColor());
    this.mRenderPaint.setStyle(Style.STROKE);

    if (trans && trans.pathValueToPixel(cubicPath)) {
      cubicPath = trans.pathValueToPixel(cubicPath);
      solidLinePath = trans.pathValueToPixel(solidLinePath);
      dashedLinePath = trans.pathValueToPixel(dashedLinePath);
    }

    Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    c.beginPath();
    c.stroke(solidLinePath);
    c.closePath();

    Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    c.beginPath();
    c.setLineDash([5,5,0]);
    c.stroke(dashedLinePath);
    c.closePath();
    this.mRenderPaint.setDashPathEffect(null);
  }

}

这个方法主要内容就是定义了两条path2D,也就是线段来绘制实线和虚线。

复制代码
//实线
let solidLinePath = new Path2D();
//虚线
let dashedLinePath = new Path2D();

绘制方法如下,

TypeScript 复制代码
solidLinePath.bezierCurveTo(
  prev.getX() + prevDx,
  (prev.getY() + prevDy) * phaseY,
  cur.getX() - curDx,
  (cur.getY() - curDy) * phaseY,
  cur.getX(),
  cur.getY() * phaseY
);

就是调用path2D的方法bezierCurveTo方法,这个方法有6个参数,分别是控制点1的(x值,y值 )和 控制点2的(x值,y值)以及目标点的(x值,y值)。直接把父类的方法抄过来即可。

我们需要有一个if判断,if(j <= dataSet.interval)就是当j小于dataSet.interval时,写绘制实线的方法,当j等于dataSet.interval时,虚线要moveTo当前位置;当j大于dataSet.interval时,就调用dashedLinePath.bezierCurveTo方法绘制虚线了。

最后绘制方法是调用c.stroke方法绘制的。c.setLineDash([5,5,0]);是设置虚线效果。

TypeScript 复制代码
 Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    c.beginPath();
    c.stroke(solidLinePath);
    c.closePath();

    Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    c.beginPath();
    c.setLineDash([5,5,0]);
    c.stroke(dashedLinePath);
    c.closePath();

最后就是使用了,代码如下:

TypeScript 复制代码
import {
  JArrayList,EntryOhos,ILineDataSet,LineData,LineChart,LineChartModel,
  Mode,
} from '@ohos/mpchart';
import { MyLineDataSet } from './MyLineDataSet';
import MyRender from './MyRender';
import data from '@ohos.telephony.data';

@Entry
@Component
struct Index {
  private model: LineChartModel = new LineChartModel();

  aboutToAppear() {
    // 创建一个 JArrayList 对象,用于存储 EntryOhos 类型的数据
    let values: JArrayList<EntryOhos> = new JArrayList<EntryOhos>();
    // 循环生成 1 到 20 的随机数据,并添加到 values 中
    for (let i = 1; i <= 20; i++) {
      values.add(new EntryOhos(i, Math.random() * 100));
    }
    // 创建 LineDataSet 对象,使用 values 数据,并设置数据集的名称为 'DataSet'
    let dataSet = new MyLineDataSet(values, 'DataSet', 6);
    dataSet.setMode(Mode.CUBIC_BEZIER);
    dataSet.setDrawCircles(false);
    dataSet.setColorByColor(Color.Blue)
    let dataSetList: JArrayList<ILineDataSet> = new JArrayList<ILineDataSet>();
    dataSetList.add(dataSet);
    // 创建 LineData 对象,使用 dataSetList数据,并将其传递给model
    let lineData: LineData = new LineData(dataSetList);
    this.model?.setData(lineData);
    this.model.setRenderer(new MyRender(this.model, this.model.getAnimator()!, this.model.getViewPortHandler()))

  }

  build() {
    Column() {
      LineChart({ model: this.model })
        .width('100%')
        .height('100%')
        .backgroundColor(Color.White)
    }
  }
}

其中最重要的就是let dataSet = new MyLineDataSet(values, 'DataSet', 6);设置了分隔点为6,以及这行代码设置了renderer类为自定义的render类:this.model.setRenderer(new MyRender(this.model, this.model.getAnimator()!, this.model.getViewPortHandler())) 。

相关推荐
长弓三石1 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
SameX3 小时前
鸿蒙 Next 电商应用安全支付与密码保护实践
前端·harmonyos
SuperHeroWu74 小时前
【HarmonyOS】键盘遮挡输入框UI布局处理
华为·harmonyos·压缩·keyboard·键盘遮挡·抬起
sanzk9 小时前
华为鸿蒙应用开发
华为·harmonyos
SoraLuna13 小时前
「Mac畅玩鸿蒙与硬件28」UI互动应用篇5 - 滑动选择器实现
macos·ui·harmonyos
ClkLog-开源埋点用户分析14 小时前
ClkLog企业版(CDP)预售开启,更有鸿蒙SDK前来助力
华为·开源·开源软件·harmonyos
mg66814 小时前
鸿蒙系统的优势 开发 环境搭建 开发小示例
华为·harmonyos
模拟IC攻城狮15 小时前
华为海思招聘-芯片与器件设计工程师-模拟芯片方向- 机试题-真题套题题目——共8套(每套四十题)
嵌入式硬件·华为·硬件架构·芯片
lqj_本人15 小时前
鸿蒙next选择 Flutter 开发跨平台应用的原因
flutter·华为·harmonyos
lqj_本人15 小时前
使用 Flutter 绘制一个棋盘
harmonyos