HarmonyOS-MPChart根据y轴刻度绘制渐变色曲线

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

mpchart本身的绘制功能是不支持颜色渐变的曲线的,只支持渐变色填充大块颜色。那么当我们的需求曲线根据y轴的刻度发生变化,就需要自定义绘制方法了。

从图中我们可以看到,左边的y轴是一个从底部到顶部颜色渐变的直线,从绿色渐变到红色,而且数据曲线根据y轴刻度做同样的渐变色。所以我们需要修改的就是两个部件的绘制效果,一个是左y轴的绘制效果,一个是数据线的绘制效果。它们涉及到两个绘制类:

复制代码
YAxisRenderer 和 LineChartRenderer

首先来看数据线的绘制方法,因为这里以曲线为例,所以我们只需要修改绘制曲线的方法,找到mpchart源码中LineChartRenderer类的drawCubicBezier方法,我们自定义一个MyDataRender类继承自LineChartRenderer类,然后将LineChartRenderer类的drawCubicBezier方法复制到自定义的类中,在其基础上做修改:

主要的修改就是将canvas的strokeStyle设置为我们通过createLinearGradient方法创建的渐变色效果。这段代码创建了一个从底部到顶部的垂直渐变,颜色从绿色到红色变化,并将这个渐变应用到了Canvas的描边样式中。

完整代码如下:

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

export default class MyDataRender extends LineChartRenderer{

  protected drawCubicBezier(c: CanvasRenderingContext2D, dataSet: ILineDataSet) {
    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();
    // cubicPath.reset();
    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);

      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 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);

    let grad = c.createLinearGradient(0, this.mChart.getHeight(), 0, 0);
    grad.addColorStop(0.0, '#00ff00')
    grad.addColorStop(1.0, '#ff0000')
    c.strokeStyle = grad;
    if (trans) {
      cubicPath = trans.pathValueToPixel(cubicPath);
    }

    Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    c.beginPath();
    c.stroke(cubicPath);
    c.closePath();
    this.mRenderPaint.setDashPathEffect(null);
  }
}

数据线的绘制方法修改完了,之后就是修改y轴的绘制方法了。与数据线的绘制类似,定义一个自定义类MyAxisRender继承自YAxisRenderer类,然后修改绘制左y轴的方法,即renderAxisLine方法:

同样是创建了一个从底部到顶部的垂直渐变,颜色从绿色到红色变化,并将这个渐变应用到了Canvas的描边样式中。

完整代码如下:

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

export default class MyAxisRender extends YAxisRenderer{
  public renderAxisLine(c: CanvasRenderingContext2D, extraLength: number): void {

    if (!this.mYAxis || !this.mViewPortHandler || !this.mYAxis.isEnabled() || !this.mYAxis.isDrawAxisLineEnabled()) {
      return;
    }

    if (this.mAxisLinePaint) {
      this.mAxisLinePaint.setColor(this.mYAxis.getAxisLineColor());
      this.mAxisLinePaint.setStrokeWidth(this.mYAxis.getAxisLineWidth());

      Utils.resetContext2DWithoutFont(c, this.mAxisLinePaint);
      if (this.mYAxis.getAxisDependency() == AxisDependency.LEFT) {
        c.beginPath()
        let grad = c.createLinearGradient(0, this.mViewPortHandler.contentBottom() + extraLength, 0, this.mViewPortHandler.contentTop());
        grad.addColorStop(0.0, '#00ff00')
        grad.addColorStop(1.0, '#ff0000')
        c.strokeStyle = grad;
        c.moveTo(this.mViewPortHandler.contentLeft(), this.mViewPortHandler.contentTop());
        c.lineTo(this.mViewPortHandler.contentLeft(), this.mViewPortHandler.contentBottom() + extraLength)
        c.stroke();
        c.closePath();
      } else {
        c.beginPath()
        c.moveTo(this.mViewPortHandler.contentRight(), this.mViewPortHandler.contentTop());
        c.lineTo(this.mViewPortHandler.contentRight(), this.mViewPortHandler.contentBottom() + extraLength)
        c.stroke();
        c.closePath();
      }
    }
  }

}

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

TypeScript 复制代码
import {
  JArrayList,EntryOhos,ILineDataSet,LineData,LineChart,LineChartModel,
  Mode,
  LineDataSet,
  AxisDependency,
  XAxisPosition,
} from '@ohos/mpchart';
import MyAxisRender from './MyAxisRender';
import data from '@ohos.telephony.data';
import MyAxisLeftRender from './MyAxisRender';
import MyDataRender from './MyDataRender';

@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 LineDataSet(values, 'DataSet');
    dataSet.setMode(Mode.CUBIC_BEZIER);
    dataSet.setDrawCircles(false);
    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.getAxisLeft()?.setAxisLineWidth(2);
    this.model.getXAxis()?.setPosition(XAxisPosition.BOTTOM);
    this.model.getAxisRight()?.setEnabled(false);
    this.model.getDescription()?.setEnabled(false);
    this.model.setRenderer(new MyDataRender(this.model, this.model.getAnimator()!, this.model.getViewPortHandler()))
    this.model.setRendererLeftYAxis(new MyAxisLeftRender(this.model.getViewPortHandler(), this.model.getAxisLeft()!, this.model.getTransformer(AxisDependency.LEFT)!))

  }

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

其中主要修改的代码就是这里,设置了绘制y轴线的类和绘制数据的类为我们自定义的两个类:

TypeScript 复制代码
this.model.setRenderer(new MyDataRender(this.model, this.model.getAnimator()!, this.model.getViewPortHandler()))
this.model.setRendererLeftYAxis(new MyAxisLeftRender(this.model.getViewPortHandler(), this.model.getAxisLeft()!, this.model.getTransformer(AxisDependency.LEFT)!))

好了,再看一遍绘制效果:

相关推荐
互联网时光机1 小时前
HarmonyOS第一课 07 从网络获取数据-习题
华为·harmonyos
鲜枣课堂2 小时前
5G-A如何与AI融合发展?华为MBBF2024给出解答
人工智能·5g·华为
似水流年QC2 小时前
初探鸿蒙:从概念到实践
华为·harmonyos
晓源よ2 小时前
huawei初级网络工程师综合实验
华为
申耀的科技观察2 小时前
【观察】华为持续投入开源开放“结硕果”,openEuler走出操作系统“创新路”...
华为·开源
华为云PaaS服务小智2 小时前
华为大咖说 | 浅谈智能运维技术
运维·华为·华为云
悟空码字2 小时前
支付宝与华为终端联手,移动支付即将进入“碰时代”
华为·鸿蒙·支付宝
HMS Core3 小时前
融合虚拟与现实,AR Engine为用户提供沉浸式交互体验
华为·ar·harmonyos
dawn13 小时前
鸿蒙ArkTS中的获取网络数据
华为·harmonyos
桃花键神13 小时前
鸿蒙5.0时代:原生鸿蒙应用市场引领开发者服务新篇章
华为·harmonyos