常见问题

1.如果去除图例?

this.model.getLegend()?.setEnabled(false);

2.如果去除右下角Description字样?

this.model.getDescription()?.setEnabled(false);

3 . 如何隐藏 Y 轴线?

this.model.getAxisLeft().setEnabled(false) //隐藏左边Y轴轴线,此时标签数字也隐藏

4.如果想隐藏轴线但是想显示数字标签?

this.model.getAxisRight().setDrawAxisLine(false);

5.如何控制 Y 轴线数据标签个数?

this.model.getAxisLeft().setLabelCount(8, false);//设置了8个

6.如何设置轴线颜色,宽度等信息?

let leftAxis = this.model.getAxisLeft();
leftAxis.setPosition(YAxisLabelPosition.OUTSIDE_CHART);//显示轴线在图表内部则使用INSIDE_CHART
this.leftAxis.setAxisLineColor(ColorTemplate.rgb("#ff0000"));//设置轴线颜色
leftAxis.setAxisLineWidth(1);// 设置轴线宽度
leftAxis.setTextSize(20);//设置y轴标签字体大小
leftAxis.setDrawGridLines(true);//设置显示网格线

7.如何自定义坐标轴标签?

如果不想用坐标轴本身的阿拉伯数字标签,也可以自定义坐标轴标签,实现方法是通过创建自定义类实现 IAxisValueFormatter 接口,修改其中的 getFormattedValue 方法,最后调用坐标轴对象的 setValueFormatter 方法就可以实现自定义坐标轴标签。

class MyAxisValueFormatter implements IAxisValueFormatter {
  getFormattedValue(value: number, axis: AxisBase): string {
    //将原本存在的对应的value转换成需要的字符串
    switch (value) {
      case 1:
        return "一";
      case 2:
        return "二";
      case 3:
        return "三";
    }
    return '';
  }
}
...
this.topAxis.setValueFormatter(new TopAxisValueFormatter())

8.图表的缩放、触摸等交互设置如何关闭或打开?

setTouchEnabled(enabled: boolean)//允许打开或者关闭与图表的所有触摸交互的情况。
setDragEnabled(enabled: boolean)//打开或关闭对图表的拖动。
setScaleEnabled(enabled: boolean)//打开或关闭对图表所有方向的缩放。
setScaleXEnabled(enabled: boolean)//打开或关闭x轴方向上的缩放
setScaleYEnabled(enabled: boolean)//打开或关闭y轴方向上的缩放。
setPinchZoom(enabled: boolean)//如果设置为true,手势捏合缩放被打开。如果设置为false,x和y轴不可以被手势捏合缩放。
setHighlightPerTapEnabled(enabled: boolean)//如果设置为true,在图表中选中触发高亮效果。
setHighlightPerDragEnabled(enabled: boolean)//设置为true时允许在手指滑动结束时显示高亮效果。默认:true
setHighlightIndicatorEnabled(enabled: boolean)//如果设置为true, 选中数据时,将展示指标线。
//此方法为dataset设置:
setVisibleXRangeMaximum(maxXRange: number) //设置x轴最多显示数据条数,(要在设置数据源后调用,否则是无效的)

9.x轴和y轴的个性化样式是怎么设置?

1)通过如下代码获取到 x 轴/y 轴对象。

//获取x轴
let xAxis = model.getXAxis();
//获取左y轴
let leftAxis = model.getAxisLeft();
//获取右y轴
let rightAxis = model.getAxisRight();

2)获取 x 轴和左右 y 轴对象之后,可以调用以下方法设置它们的属性

setEnabled(enabled: boolean)//设置轴是否被绘制。默认绘制,设置为false则不会被绘制。
setDrawLabels(enabled: boolean)//设置为true则绘制轴的标签。
setDrawAxisLine(enabled: boolean)//设置为true则绘制轴线。
setDrawGridLines(enabled: boolean)//设置为true则绘制网格线。
setTextColor(color: string | number | CanvasGradient | CanvasPattern)//设置轴标签文本颜色。
setTextSize(size: number)//设置轴标签的字体大小。
setTypeface(tf: FontFamily)//设置轴标签的FontFamily,指定字体系列,支持如下几种类型:'sans-serif', 'serif', 'monospace'。
setGridColor(color: number)//设置网格线颜色。
setGridLineWidth(width: number)//设置网格线宽度。
setAxisLineColor(color: number)//设置坐标轴的颜色。
setAxisLineWidth(width: number)//设置坐标轴的宽度。
enableGridDashedLine(lineLength: number, spaceLength: number, phase: number)//设置网格线虚线样式,"lineLength"控制短线条的长度,"spaceLength"控制两段线之间的间隔长度,"phase"控制开始的点。
setAxisMaxValue(max: number)//设置一个自定义的最大值,如果设置了数值,这个值将不会依赖于提供的数据自动计算。
setAxisMinValue(min: number)//设置一个自定义的最小值。如果设置了数值,这个值将不会依赖于提供的数据进行自动计算。

3)x 轴专属设置:

setAvoidFirstLastClipping(enabled: boolean)//如果设置为true,图表将避免在图表或屏幕的边缘的标签条目被裁剪掉。
setPosition(pos: XAxisPosition)//设置XAxis应该出现的位置。可以选择TOP,BOTTOM,BOTH\_SIDED,TOP\_INSIDE或者BOTTOM\_INSIDE。

4)y轴专属设置:

setInverted(enabled: boolean)//如果设置为true,这个轴将被反向,那意味着最大值将被放到底部,最小值将被放到顶部。
setSpaceTop(percent: number)//设置在图表上最高处的值相比轴上最高值的顶端空间(占总轴范围的百分比)。
setSpaceBottom(percent: number)//设置在图表上最低处的值相比轴上最低处值的底部空间(总轴范围的百分比)。
setPosition(pos: YAxisLabelPosition)//设置轴标签应该被绘制的位置。INSIDE\_CHART或者OUTSIDE\_CHART中的一个。

10.线形图的选中横竖线怎么关闭?

可以根据需要选择性地关闭水平和垂直高亮线。通过数据集(DataSet)的以下方法进行配置:

  • setDrawHighlightIndicators(boolean):启用或禁用高亮指示器(同时关闭水平和垂直高亮线)。
  • setDrawVerticalHighlightIndicator(boolean):启用或禁用垂直高亮线。
  • setDrawHorizontalHighlightIndicator(boolean):启用或禁用水平高亮线。

DD一下: 鸿蒙开发各类文档,可关注公众号<程序猿百晓生>获取。

1.OpenHarmony开发基础
2.OpenHarmony北向开发环境搭建
3.鸿蒙南向开发环境的搭建
4.鸿蒙生态应用开发白皮书V2.0 & V3.0
5.鸿蒙开发面试真题(含参考答案) 
6.TypeScript入门学习手册
7.OpenHarmony 经典面试题(含参考答案)
8.OpenHarmony设备开发入门【最新版】
9.沉浸式剖析OpenHarmony源代码
10.系统定制指南
11.【OpenHarmony】Uboot 驱动加载流程
12.OpenHarmony构建系统--GN与子系统、部件、模块详解
13.ohos开机init启动流程
14.鸿蒙版性能优化指南
.......

常见自定义图表

1.绘制虚实相接的曲线图

参考代码位置:entry/src/main/ets/pages/customCharts/DashedSolidLinePage

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

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

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类的代码如下:

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

绘制方法如下:

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]);是设置虚线效果。

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

最后的使用代码如下:

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

2.左y轴显示数值,右y轴显示百分比

参考代码位置:entry/src/main/ets/pages/customCharts/LeftRightAxisPage.ets

左y轴数值不变,右y轴改成百分比,需要通过自定义RightAxisFormatter实现IAxisValueFormatter接口,将右y轴的数值改成百分比文本,RightAxisFormatter类如下:

class RightAxisFormatter implements IAxisValueFormatter {
  maxNumber: number = 0;
  constructor(maxNumber: number) {
    this.maxNumber = maxNumber;
  }
  getFormattedValue(value: number, axis: AxisBase): string {
    switch (value) {
      case this.maxNumber:
        return "100%";
      case this.maxNumber * 4 / 5:
        return "80%";
      case this.maxNumber * 3 / 5:
        return "60%";
      case this.maxNumber * 2 / 5:
        return "40%";
      case this.maxNumber * 1 / 5:
        return "20%";
      case 0:
        return "0%";
    }
    return "";
  }
}

使用方法如下:

//设置标签文本转换器
rightAxis?.setValueFormatter(new RightAxisFormatter(this.maxNumber));

3.短刻度线

参考代码位置:entry/src/main/ets/pages/customCharts/ScaleLinePage

若需绘制x轴的短刻度线,可以利用现有资源,将原本的网格线稍作修改,改成绘制一条短线即可。

具体的方法就是写一个类MyXAxisRender继承自XAxisRenderer,重写父类的drawGridLine方法,

代码如下:

import { Utils, XAxisRenderer } from '@ohos/mpchart';

export default class MyXAxisRender extends XAxisRenderer{
  protected drawGridLine(c: CanvasRenderingContext2D, x: number, y: number): void {
    Utils.resetContext2DWithoutFont(c, this.mGridPaint);
    c.beginPath();
    let bottom = this.mViewPortHandler?.contentBottom()??0;
    c.moveTo(x, bottom);
    c.lineTo(x, bottom - 10);
    c.stroke();
    c.closePath();
  }
}

其中关键代码就是c.moveTo(x,bottom)和c.lineTo(x, bottom-10),这两行代码决定了刻度线绘制的位置和长短,即从图表底部开始往上绘制一条长度为10vp的线条。

那么使用的时候,就通过以下代码将X轴的绘制类改成自定义的绘制类 MyXAxisRender:

this.model.setXAxisRenderer(new MyXAxisRender(this.model.getViewPortHandler(), this.model.getXAxis()!, this.model.getTransformer(AxisDependency.LEFT)!));

4.根据y轴刻度绘制渐变色曲线

参考代码位置:entry/src/main/ets/pages/customCharts/GradientLinePage

渐变色曲线的参考代码修改了两个部件的绘制效果:一个是左y轴的绘制效果,另一个是数据线的绘制效果。这两个部件涉及到YAxisRenderer和LineChartRenderer两个绘制类。

首先,修改左y轴的绘制效果需要创建一个继承自YAxisRenderer类的自定义类MyAxisRender,并修改其中的renderAxisLine方法来改变y轴的绘制效果。修改的主要内容是将canvas的strokeStyle设置为通过createLinearGradient方法创建的渐变色效果,并将其应用到y轴线的描边样式中。这样,左边的y轴会呈现出颜色渐变的效果。

接着,数据线的绘制效果,需要创建一个继承自LineChartRenderer类的自定义类MyDataRender,并修改其中的drawCubicBezier方法。创建一个从底部到顶部的垂直渐变,然后将这个渐变应用到数据线的描边样式中。