HarmonyOS-MPChart以X轴或y轴为区间设置不同颜色

        本文是基于鸿蒙三方库mpchart OpenHarmony-SIG/ohos-MPChart 的使用,以X轴为区间设置不同的曲线颜色。

        mpchart本身的绘制功能是不支持不同区间颜色不同的曲线的,那么当我们的需求曲线根据x轴的刻度区间绘制不同颜色,就需要自定义绘制方法了。

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

        本文的示例是绘制两段颜色不一样的曲线,并且以x=10为分界点,所以只需要新建一个Path2D对象cubicPath2,记录x>10以后的曲线就可以了,前半段曲线就是原有的cubicPath,当j==10的时候,开始记录cubicPath2的起始点,即通过moveTo方法移动到起始点,代码如下:

之后就是新启一个for循环,将cubicPath2的线条继续画完,这样到最后,我们就得到了两个Path2D对象,一个是x = 10以前的cubicPath对象,另一个是x = 10以后的cubicPath2对象,代码如下:

建立并赋值两个Path2D对象之后,就可以开始为它们绘制不同颜色的值了:

其中cubicPath绘制为红色,cubicPath2绘制为绿色,完整代码如下:

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();
    let cubicPath2 = 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);

      for (let j: number = this.mXBounds.min + 1; j <= 10; 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 == 10){
          cubicPath2.moveTo(cur.getX(), cur.getY() * phaseY);
        }
      }


      for (let j: number = 11; 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;

        cubicPath2.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) {
      cubicPath = trans.pathValueToPixel(cubicPath);
      cubicPath2 = trans.pathValueToPixel(cubicPath2);
    }

    Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    c.beginPath();
    c.strokeStyle = "#f00";
    c.stroke(cubicPath);
    c.closePath();

    Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    c.beginPath();
    c.strokeStyle = "#0f0";
    c.stroke(cubicPath2);
    c.closePath();
    this.mRenderPaint.setDashPathEffect(null);
  }
}

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

import {
  JArrayList,EntryOhos,ILineDataSet,LineData,LineChart,LineChartModel,
  Mode,
  LineDataSet,
  XAxisPosition,
} from '@ohos/mpchart';
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()))

  }

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

本文的示例线段颜色分隔点和颜色段数都是写的固定的,如果需要不固定的,在设置数据的时候就传入分隔点和颜色值,可以自行定义一个MyLineDataSet类,继承自LineDataSet,重写其构造方法和并增加属性值。

类似上篇文章讲的虚实相接曲线的写法:

然后在设置数据的时候用自定义的MyLineDataSet类就可以了。

 以下是y轴的刻度范围来动态调整曲线的颜色的方法

  

        若需根据y轴的刻度范围来动态调整曲线的颜色,可以借鉴下面代码的实现方式。具体而言,在代码中使用0.5作为分界点,这代表了一个比例值,即图表高度的50%处。如果我们将y轴的刻度范围映射到百分比上,其中最低点设为0%(对应y值的最小值),最高点设为100%(对应y值的最大值),那么当y值小于或等于50%(即0.5)时,曲线颜色为红色;而当y值大于50%时,曲线颜色则变为蓝色。这样,0.5作为颜色转换的分界点,直接对应于y轴上50%的位置,实现了根据y值动态改变曲线颜色的效果。

这个方法可以获取y轴最大最小值:

this.mChart.getAxis(AxisDependency.LEFT)?.getAxisMaximum();
this.mChart.getAxis(AxisDependency.LEFT)?.getAxisMinimum();

完整代码如下:

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, '#f00')
    grad.addColorStop(0.5, "#f00")

    grad.addColorStop(0.5, "#00f")
    grad.addColorStop(1.0, '#00f')
    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);
  }
}
  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值