9afce6f6创建于 2025年5月7日历史提交
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {
  JArrayList,
  XAxis,
  XAxisPosition,
  YAxis,
  Description,
  Legend,
  EntryOhos,
  YAxisLabelPosition,
  LineDataSet,
  ILineDataSet,
  LineData,
  Mode,
  LineChart,
  LineChartModel,
  ChartColorStop,
  LegendForm,
  IAxisValueFormatter,
  AxisBase,
  LegendOrientation,
  LegendVerticalAlignment,
  LegendHorizontalAlignment,
} from '@ohos/mpchart';
import { EventType } from '@ohos/mpchart/src/main/ets/components/listener/EventControl';
import { CustomUiInfo } from '../model/BasicDataSource';

const HEART_RATE: string = 'heartRate';
const REFERENCE: string = '参考';
const HEART_FILL_COLOR1: string = '#FFFDFCF5';
const STEP_FILL_COLOR1: string = '#0C0099CC';
const HEART_FILL_COLOR2: string = '#FFFBF4DE';
const STEP_FILL_COLOR2: string = '#7F0099CC';
const HEART_FILL_COLOR3: string = '#FFFAE8C2';
const STEP_FILL_COLOR3: string = '#0099CC';
const TEXT_SIZE: number = 14;
const CUSTOM_WIDTH: number = 90;
const CUSTOM_HEIGHT: number = 50;
const HEART_RATES: string = '心率';
const STEP_NUMBER: string = '步数';

/**
 * 场景描述:当前组件为线形图组件 LineChartModel。
 * 构造新的对象模型后通过模型的方法设置属性。
 * 具体实现见aboutToAppear内注释。
 */
@Component
export struct LineCharts {
  @Prop type: string = HEART_RATE; // 设置线形图类型(心率或步数)
  @Prop referenceData: Array<number | null>; // 参考数据
  @Prop todayData: Array<number | null>; // 当日数据
  model: LineChartModel | null = null; // 线形图模型
  private leftAxis: YAxis | null = null; // 左侧Y轴数据
  private rightAxis: YAxis | null = null; // 右侧Y轴数据
  private xAxis: XAxis | null = null; // X轴数据
  @State lineData: LineData = new LineData(); // 线形图数据
  @State customUiInfo: CustomUiInfo = new CustomUiInfo(HEART_RATE, CUSTOM_WIDTH, CUSTOM_HEIGHT); // 图表的Marker(标志气泡)组件

  // 图表Marker(标志气泡)组件
  @Builder
  customUi() {
    // 是否在图表content内
    if (this.customUiInfo.isInbounds && this.customUiInfo.data) {
      Column() {
        Text(this.customUiInfo.getFormattedValue())
          .fontColor(Color.White)
          .fontSize($r('app.integer.bar_chart_health_font_size_twelve'))
          .fontWeight(FontWeight.Bold)
        Text(`${this.customUiInfo.type === HEART_RATE ? HEART_RATES : STEP_NUMBER}: ${JSON.stringify(this.customUiInfo.data.getY())}`)
          .fontColor(Color.White)
          .fontSize($r('app.integer.bar_chart_health_font_size_twelve'))
      }
      .padding($r('app.integer.bar_chart_health_padding_four'))
      .borderRadius($r('app.integer.bar_chart_health_radius_six'))
      .border({
        width: $r('app.integer.bar_chart_health_border_width_one'),
        color: this.customUiInfo.type === HEART_RATE ? Color.Orange : Color.Blue
      })
      .backgroundColor($r('app.color.bar_chart_health_bg_color'))
      .width(this.customUiInfo.width)
      .height(this.customUiInfo.height)
      .margin({ left: this.customUiInfo.x, top: this.customUiInfo.y })
      .alignItems(HorizontalAlign.Start)
      .justifyContent(FlexAlign.Center)
      .onClick(() => {
        this.customUiInfo.showUi = false;
      })
    }
  }

  // 图表数据初始化
  aboutToAppear(): void {
    // 构建Marker组件
    this.customUiInfo = new CustomUiInfo(this.type, CUSTOM_WIDTH, CUSTOM_HEIGHT);
    // TODO 知识点:必须初始化图表配置构建类
    this.model = new LineChartModel();
    this.model.setPinchZoom(false);
    this.model.setDrawGridBackground(false);
    // TODO 知识点:配置图表指定样式,各部件间没有先后之分
    // 获取图表描述部件,设置图表描述部件不可用,即图表不进行绘制描述部件
    const description: Description | null = this.model.getDescription();
    if (description) {
      description.setEnabled(false);
    }
    // 设置X轴信息
    this.xAxis = this.model.getXAxis();
    if (this.xAxis) {
      //设置标签位置
      this.xAxis.setPosition(XAxisPosition.BOTTOM);
      // 设置X轴是否绘制网格线
      this.xAxis.setDrawGridLines(true);
      this.xAxis.setGranularity(1);
      // 设置数据的格式转换器
      this.xAxis.setValueFormatter(new XValueFormatter());
      // 设置绘制标签个数
      this.xAxis.setLabelCount(10);
      this.xAxis.enableGridDashedLine(2, 2, 0);
    }
    // 设置图表左Y轴信息
    this.leftAxis = this.model.getAxisLeft();
    if (this.leftAxis) {
      this.leftAxis.setLabelCount(4, true);
      this.leftAxis.setDrawGridLines(true);
      // 设置图表左Y轴是否在数据后绘制限制线
      this.leftAxis.setDrawGridLinesBehindData(true);
      this.leftAxis.setPosition(YAxisLabelPosition.OUTSIDE_CHART);
      this.leftAxis.setAxisMinimum(0);
      // 设置图表左Y轴数据的格式转换器
      this.leftAxis.setValueFormatter(new YValueFormatter());
      this.leftAxis.setEnabled(true);
      this.leftAxis.enableGridDashedLine(2, 2, 0);
    }
    // 设置图表右Y轴信息
    this.rightAxis = this.model.getAxisRight();
    if (this.rightAxis) {
      // 设置图表右Y轴是否显示
      this.rightAxis.setEnabled(false);
    }
    // 获取图表图例部件,设置图表图例部件不可用
    const legend: Legend | null = this.model.getLegend();
    if (legend) {
      legend.setEnabled(true);
      // 设置图例类型
      legend.setForm(LegendForm.LINE);
      // 设置图例文本大小
      legend.setTextSize(TEXT_SIZE);
      // 设置图例方向为水平
      legend.setOrientation(LegendOrientation.HORIZONTAL);
      // 设置图例垂直对齐方式为顶部
      legend.setVerticalAlignment(LegendVerticalAlignment.TOP);
      // 设置图例水平对齐方式为左对齐
      legend.setHorizontalAlignment(LegendHorizontalAlignment.LEFT);
    }
    // TODO 知识点:将数据与图表配置类绑定
    this.model.setData(this.lineData);
    // 设置模型是否可缩放
    this.model.setScaleEnabled(false);

    this.lineData = this.getLineData();
    if (this.model) {
      this.model.setData(this.lineData);
      this.model.invalidate();
    }
  }

  // 生成线形图数据
  private getLineData(): LineData {
    const START: number = 0;
    const values: JArrayList<EntryOhos> = new JArrayList<EntryOhos>();
    const values2: JArrayList<EntryOhos> = new JArrayList<EntryOhos>();
    for (let i = START; i < this.todayData.length; i++) {
      values.add(new EntryOhos(i, this.todayData[i]));
    }
    for (let i = START; i < this.referenceData.length; i++) {
      values2.add(new EntryOhos(i, this.referenceData[i]));
    }
    const dataSetList: JArrayList<ILineDataSet> = new JArrayList<ILineDataSet>();
    const dataSet = new LineDataSet(values, '今日');
    // 设置数据高亮颜色
    dataSet.setHighLightColor(Color.Red);
    // 设置数据高亮线的宽度
    dataSet.setHighlightLineWidth(0.1);
    // 不绘制数据值
    dataSet.setDrawValues(false);
    dataSet.setLineWidth(1.5);
    dataSet.setDrawIcons(false);
    // 设置曲线为贝塞尔曲线模式
    dataSet.setMode(Mode.CUBIC_BEZIER);
    // 折线点不画圆圈
    dataSet.setDrawCircles(false);
    // 设置曲线颜色
    dataSet.setColorByColor(this.customUiInfo.type === HEART_RATE ? Color.Orange : Color.Blue);
    const gradientFillColor = new JArrayList<ChartColorStop>();
    gradientFillColor.add([this.customUiInfo.type === HEART_RATE ? HEART_FILL_COLOR3 : STEP_FILL_COLOR3, 0.2]);
    gradientFillColor.add([this.customUiInfo.type === HEART_RATE ? HEART_FILL_COLOR2 : STEP_FILL_COLOR2, 0.6]);
    gradientFillColor.add([this.customUiInfo.type === HEART_RATE ? HEART_FILL_COLOR1 : STEP_FILL_COLOR1, 1.0]);
    // 设置渐变色填充
    dataSet.setGradientFillColor(gradientFillColor);
    dataSet.setDrawFilled(true);
    dataSetList.add(dataSet);
    const dataSet2 = new LineDataSet(values2, REFERENCE);
    dataSet2.setHighLightColor(Color.Black);
    dataSet2.setHighlightLineWidth(0.1);
    dataSet2.setDrawValues(false);
    dataSet2.setLineWidth(1.5);
    dataSet2.setDrawIcons(false);
    dataSet2.setMode(Mode.CUBIC_BEZIER);
    dataSet2.setDrawCircles(false);
    dataSet2.setColorByColor(Color.Green);
    dataSetList.add(dataSet2);
    return new LineData(dataSetList);
  }

  build() {
    LineChart({
      model: this.model,
      // 自定义 ui: 传入 builder
      customUiBuilder: this.customUi,
      // 通过什么事件触发
      customUiTriggerEvent: EventType.SingleTap,
      // 自定义ui的位置信息
      customUiInfo: this.customUiInfo,
    })
      .id(this.type === HEART_RATE ? 'heartRate' : 'stepNumber')
      .width($r('app.string.bar_chart_health_layout_full_size'))
      .height($r('app.string.bar_chart_health_layout_forty'))
      .margin({ top: $r('app.integer.bar_chart_health_margin_five'), bottom: $r('app.integer.bar_chart_health_margin_twenty') })
  }
}

// 设置X轴数据的格式转换器
class XValueFormatter implements IAxisValueFormatter {
  getFormattedValue(value: number, axis: AxisBase): string {
    switch (value) {
      case 0:
        return '0:00';
      case 3:
        return '3:00';
      case 6:
        return '6:00';
      case 9:
        return '9:00';
      case 12:
        return '12:00';
      case 15:
        return '15:00';
      case 18:
        return '18:00';
      case 21:
        return '21:00';
      case 24:
        return '+1';
      case 27:
        return '3:00';
    }
    return value + '';
  }
}

// 设置Y轴数据的格式转换器
class YValueFormatter implements IAxisValueFormatter {
  getFormattedValue(value: number, axis: AxisBase): string {
    return value.toFixed(0);
  }
}