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


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

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

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

    1. import { EntryOhos, JArrayList, LineDataSet } from '@ohos/mpchart';
    2. export class MyLineDataSet extends LineDataSet {
    3. interval: number = 0;
    4. constructor(yVals: JArrayList | null, label: string, interval: number) {
    5. super(yVals, label);
    6. this.interval = interval;
    7. }
    8. }

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

    1. import { EntryOhos, ILineDataSet, Style, Transformer, Utils, LineChartRenderer } from '@ohos/mpchart';
    2. import { MyLineDataSet } from './MyLineDataSet';
    3. export default class MyRender extends LineChartRenderer{
    4. protected drawCubicBezier(c: CanvasRenderingContext2D, dataSet: MyLineDataSet) {
    5. if(dataSet.interval == undefined){
    6. super.drawCubicBezier(c, dataSet);
    7. return;
    8. }
    9. if (!this.mChart || !this.mXBounds) {
    10. return;
    11. }
    12. const phaseY: number = this.mAnimator ? this.mAnimator.getPhaseY() : 1;
    13. const trans: Transformer | null = this.mChart.getTransformer(dataSet.getAxisDependency());
    14. this.mXBounds.set(this.mChart, dataSet);
    15. const intensity: number = dataSet.getCubicIntensity();
    16. let cubicPath = new Path2D();
    17. //实线
    18. let solidLinePath = new Path2D();
    19. //虚线
    20. let dashedLinePath = new Path2D();
    21. if (this.mXBounds.range >= 1) {
    22. let prevDx: number = 0;
    23. let prevDy: number = 0;
    24. let curDx: number = 0;
    25. let curDy: number = 0;
    26. // Take an extra point from the left, and an extra from the right.
    27. // 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.
    28. // So in the starting `prev` and `cur`, go -2, -1
    29. // And in the `lastIndex`, add +1
    30. const firstIndex: number = this.mXBounds.min + 1;
    31. const lastIndex: number = this.mXBounds.min + this.mXBounds.range;
    32. let prevPrev: EntryOhos | null;
    33. let prev: EntryOhos | null = dataSet.getEntryForIndex(Math.max(firstIndex - 2, 0));
    34. let cur: EntryOhos | null = dataSet.getEntryForIndex(Math.max(firstIndex - 1, 0));
    35. let next: EntryOhos | null = cur;
    36. let nextIndex: number = -1;
    37. if (cur === null) return;
    38. Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    39. // let the spline start
    40. cubicPath.moveTo(cur.getX(), cur.getY() * phaseY);
    41. solidLinePath.moveTo(cur.getX(), cur.getY() * phaseY);
    42. for (let j: number = this.mXBounds.min + 1; j <= this.mXBounds.range + this.mXBounds.min; j++) {
    43. prevPrev = prev;
    44. prev = cur;
    45. cur = nextIndex === j ? next : dataSet.getEntryForIndex(j);
    46. nextIndex = j + 1 < dataSet.getEntryCount() ? j + 1 : j;
    47. next = dataSet.getEntryForIndex(nextIndex);
    48. prevDx = (cur.getX() - prevPrev.getX()) * intensity;
    49. prevDy = (cur.getY() - prevPrev.getY()) * intensity;
    50. curDx = (next.getX() - prev.getX()) * intensity;
    51. curDy = (next.getY() - prev.getY()) * intensity;
    52. cubicPath.bezierCurveTo(
    53. prev.getX() + prevDx,
    54. (prev.getY() + prevDy) * phaseY,
    55. cur.getX() - curDx,
    56. (cur.getY() - curDy) * phaseY,
    57. cur.getX(),
    58. cur.getY() * phaseY
    59. );
    60. if(j <= dataSet.interval){
    61. solidLinePath.bezierCurveTo(
    62. prev.getX() + prevDx,
    63. (prev.getY() + prevDy) * phaseY,
    64. cur.getX() - curDx,
    65. (cur.getY() - curDy) * phaseY,
    66. cur.getX(),
    67. cur.getY() * phaseY
    68. );
    69. if(j == dataSet.interval) {
    70. dashedLinePath.moveTo(cur.getX(),
    71. cur.getY() * phaseY);
    72. }
    73. }else{
    74. dashedLinePath.bezierCurveTo(
    75. prev.getX() + prevDx,
    76. (prev.getY() + prevDy) * phaseY,
    77. cur.getX() - curDx,
    78. (cur.getY() - curDy) * phaseY,
    79. cur.getX(),
    80. cur.getY() * phaseY
    81. );
    82. }
    83. }
    84. }
    85. // if filled is enabled, close the path
    86. if (dataSet.isDrawFilledEnabled()) {
    87. let cubicFillPath: Path2D = new Path2D();
    88. // cubicFillPath.reset();
    89. cubicFillPath.addPath(cubicPath);
    90. if (c && trans) {
    91. this.drawCubicFill(c, dataSet, cubicFillPath, trans, this.mXBounds);
    92. }
    93. }
    94. this.mRenderPaint.setColor(dataSet.getColor());
    95. this.mRenderPaint.setStyle(Style.STROKE);
    96. if (trans && trans.pathValueToPixel(cubicPath)) {
    97. cubicPath = trans.pathValueToPixel(cubicPath);
    98. solidLinePath = trans.pathValueToPixel(solidLinePath);
    99. dashedLinePath = trans.pathValueToPixel(dashedLinePath);
    100. }
    101. Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    102. c.beginPath();
    103. c.stroke(solidLinePath);
    104. c.closePath();
    105. Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    106. c.beginPath();
    107. c.setLineDash([5,5,0]);
    108. c.stroke(dashedLinePath);
    109. c.closePath();
    110. this.mRenderPaint.setDashPathEffect(null);
    111. }
    112. }

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

    //实线
    let solidLinePath = new Path2D();
    //虚线
    let dashedLinePath = new Path2D();

    绘制方法如下,

    1. solidLinePath.bezierCurveTo(
    2. prev.getX() + prevDx,
    3. (prev.getY() + prevDy) * phaseY,
    4. cur.getX() - curDx,
    5. (cur.getY() - curDy) * phaseY,
    6. cur.getX(),
    7. cur.getY() * phaseY
    8. );

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

    1. Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    2. c.beginPath();
    3. c.stroke(solidLinePath);
    4. c.closePath();
    5. Utils.resetContext2DWithoutFont(c, this.mRenderPaint);
    6. c.beginPath();
    7. c.setLineDash([5,5,0]);
    8. c.stroke(dashedLinePath);
    9. c.closePath();

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

    1. import {
    2. JArrayList,EntryOhos,ILineDataSet,LineData,LineChart,LineChartModel,
    3. Mode,
    4. } from '@ohos/mpchart';
    5. import { MyLineDataSet } from './MyLineDataSet';
    6. import MyRender from './MyRender';
    7. import data from '@ohos.telephony.data';
    8. @Entry
    9. @Component
    10. struct Index {
    11. private model: LineChartModel = new LineChartModel();
    12. aboutToAppear() {
    13. // 创建一个 JArrayList 对象,用于存储 EntryOhos 类型的数据
    14. let values: JArrayList<EntryOhos> = new JArrayList<EntryOhos>();
    15. // 循环生成 1 到 20 的随机数据,并添加到 values 中
    16. for (let i = 1; i <= 20; i++) {
    17. values.add(new EntryOhos(i, Math.random() * 100));
    18. }
    19. // 创建 LineDataSet 对象,使用 values 数据,并设置数据集的名称为 'DataSet'
    20. let dataSet = new MyLineDataSet(values, 'DataSet', 6);
    21. dataSet.setMode(Mode.CUBIC_BEZIER);
    22. dataSet.setDrawCircles(false);
    23. dataSet.setColorByColor(Color.Blue)
    24. let dataSetList: JArrayList<ILineDataSet> = new JArrayList<ILineDataSet>();
    25. dataSetList.add(dataSet);
    26. // 创建 LineData 对象,使用 dataSetList数据,并将其传递给model
    27. let lineData: LineData = new LineData(dataSetList);
    28. this.model?.setData(lineData);
    29. this.model.setRenderer(new MyRender(this.model, this.model.getAnimator()!, this.model.getViewPortHandler()))
    30. }
    31. build() {
    32. Column() {
    33. LineChart({ model: this.model })
    34. .width('100%')
    35. .height('100%')
    36. .backgroundColor(Color.White)
    37. }
    38. }
    39. }

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

  • 相关阅读:
    Open CASCADE学习|为什么由Edge生成Wire不成功?
    什么是 Linux Mint,它比 Ubuntu 好在哪里?
    Flink学习第十天——玩转Flink Core Api常用Transformation算子 多案例实战
    aspectj切面织入
    这次我设计了一款TPS百万级别的分布式、高性能、可扩展的RPC框架
    基于.NetCore开源的Windows的GIF录屏工具
    LeetCode300:最长递增子序列
    ARM裸机二
    这个报错是什么意思啊
    Java IO:异常处理的简介说明
  • 原文地址:https://blog.csdn.net/liuhaikang/article/details/139051250