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

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

相关推荐
以太浮标16 小时前
华为eNSP模拟器综合实验之-DHCP服务中继配置案例
网络·华为·智能路由器·信息与通信
游戏技术分享16 小时前
【鸿蒙游戏技术分享 第75期】AGC后台批量导入商品失败,提示“参数错误”
游戏·华为·harmonyos
No Silver Bullet16 小时前
HarmonyOS NEXT开发进阶(十七):WebView 拉起 H5 页面
华为·harmonyos
liuhaikang17 小时前
【鸿蒙HarmonyOS Next App实战开发】口语小搭档——应用技术实践
harmonyos
北方的流星18 小时前
华为交换机MSTP和VRRP综合应用配置
运维·网络·华为
C雨后彩虹20 小时前
简易内存池
java·数据结构·算法·华为·面试
liuhaikang20 小时前
鸿蒙VR视频播放库——md360player
音视频·vr·harmonyos
北方的流星1 天前
华为交换机IPv6静态路由、默认路由、RIPng和OSPFv3路由配置
运维·网络·华为
飞露1 天前
鸿蒙Preview预览文件失败原因
华为·harmonyos
夏小鱼的blog1 天前
【HarmonyOS应用开发入门】第五期:状态管理V2入门 - 1
harmonyos·状态管理