DEV Community

HarmonyOS
HarmonyOS

Posted on

How to Create an Interactive Trivia Wheel on Wearables with mpchart?

Read the original article:How to Create an Interactive Trivia Wheel on Wearables with mpchart?

How to Create an Interactive Trivia Wheel on Wearables with mpchart?

Requirement Description

This article describes how to implement an interactive, spinning trivia wheel application on a wearable device. The application, built with ArkUI, utilizes the @ohos/mpchart library to display a pie chart. When a user taps a button, the chart spins and randomly selects a trivia category, which is then displayed on the screen.

Background Knowledge

Wearable devices present unique challenges due to their limited screen size and processing power. Effectively visualizing data and creating engaging user experiences within these constraints is crucial. The @ohos/mpchart library, a port of the popular openharmony library, is an excellent choice for this purpose. It offers a wide range of chart types and customization options, while also being optimized for performance. ArkUI's declarative nature simplifies the UI development process, allowing for the creation of fluid and responsive interfaces.

Implementation Steps

  1. Project Setup: Ensure the @ohos/mpchart dependency is added to your project's oh-package.json5 file.
  2. Chart Initialization: In the aboutToAppear lifecycle hook, an instance of PieChartModel is created. Key properties are configured to disable the description, legend, hole, and user touch interactions, creating a clean, focused visual.
  3. Data Population: The setData method populates the pie chart with data. It creates PieEntry objects for each trivia category, adds them to a PieDataSet, and assigns custom colors to each slice.
  4. Spin Animation Logic: The startGun method handles the spinning animation. It uses the animator module to create a rotation animation with a defined duration and easing curve. This creates a realistic "spin-down" effect.
  5. Result Calculation: Once the animation is complete (onfinish), the final rotation angle is used to determine which pie slice the pointer is aligned with. The currentIndex is calculated to correspond to the selected category from the parties array.
  6. UI Interaction: A button is placed at the center of the pie chart. Tapping this button triggers the startGun method, initiating the spin. The selected topic's name is then displayed, providing instant feedback to the user.

Code Snippet / Configuration

import {
  JArrayList,
  MPPointF,
  PieChart,
  PieChartModel,
  PieData,
  PieDataSet,
  PieEntry
} from '@ohos/mpchart';
import animator, { AnimatorOptions, AnimatorResult } from '@ohos.animator';

@Entry
@Component
export default struct Index {

  model: PieChartModel = new PieChartModel();
  private animatorResult: AnimatorResult | null = null
  @State chooseText: string = 'Spin';
  @State onePartAngle: number = 0;
  @State count: number = 6;
  @State currentIndex: number = 0;
  parties: string[] = ['History', 'Science', 'Art', 'Sports', 'Cinema', 'Music'];
  @State isSpinning:boolean = true;
  @State topic:string = ''
  @State selectedTopic:string = ''

  aboutToAppear(): void {
    this.init();
  }

  build() {
    NavDestination(){
      Column() {
        Stack(){
          Stack() {
            PieChart({ model: this.model })
              .width('99%')
              .aspectRatio(1)
            Text('')
              .fontSize(26)
              .fontWeight(FontWeight.Bolder)
              .alignSelf(ItemAlign.Start)
              .height('100%')
              .textAlign(TextAlign.Start)
              .align(Alignment.Top)
              .margin({ top: -15 })


            Button(){
              Text(this.chooseText).fontWeight(FontWeight.Bold)
            }
            .onClick(() => {
              this.startGun();
            })
            .alignSelf(ItemAlign.Center)
            .fontColor(Color.White)
            .fontWeight(FontWeight.Bold)
            .width(75)
            .height(75)
            .borderRadius(200)
            .backgroundColor('#2071ff')
            .align(Alignment.Center)
            .shadow({
              offsetX: 1,
              offsetY: 1,
              color: Color.Black,
              radius: 10
            })


          }.height('100%')

        }.align(Alignment.Bottom)

      }
      .justifyContent(FlexAlign.Center)
      .width('100%')
      .height('100%')
      .backgroundColor(Color.Black)
    }
    .hideTitleBar(true);

  }

  init() {
    this.model.getDescription()?.setEnabled(false);
    this.model.setDrawHoleEnabled(false);
    this.model.getLegend()?.setEnabled(false);
    this.model.disableScroll();
    this.model.setRotationEnabled(true);
    this.model.setHighlightPerTapEnabled(true);
    this.model.setEntryLabelTextSize(14)
    this.model.setEntryLabelColor(Color.White)
    this.model.setTouchEnabled(false);
    this.setData(5);
  }

  private async setData(range: number): Promise<void> {
    let entries: JArrayList<PieEntry> = new JArrayList<PieEntry>();
    for (let i = 0; i < this.count; i++) {
      entries.add(new PieEntry(range,
        this.parties[i % this.parties.length]));
    }

    let dataSet: PieDataSet = new PieDataSet(entries, "Election Results");
    dataSet.setSliceSpace(1);
    dataSet.setIconsOffset(new MPPointF(0, 40));
    dataSet.setSelectionShift(0);
    dataSet.setValueTextColor(Color.White);
    dataSet.setDrawValues(false);

    const customColors: number[] = [
      0x9066bd,
      0xB572CA,
      0xee66bb,
      0xf4777c,
      0xF7AC7C,
      0xFAC166
    ];

    let colors: JArrayList<number> = new JArrayList();
    for (let color of customColors) {
      colors.add(color);
    }

    dataSet.setColorsByList(colors);
    let data: PieData = new PieData(dataSet);
    this.onePartAngle = 360 / this.count;
    this.model.setRotationAngle(-90 - this.onePartAngle / 2);
    this.model.setData(data);
  }

  startGun() {
    this.isSpinning = true
    let currentAngle = this.model.getRotationAngle();
    let randomRotation = Math.floor(Math.random() * 360);
    let end = 7200 + currentAngle + randomRotation;

    let options: AnimatorOptions = {
      duration: 11000,
      easing: "ease",
      delay: 0,
      fill: "forwards",
      direction: "normal",
      begin: currentAngle,
      end: end,
      iterations: 1
    };

    this.animatorResult = animator.create(options);
    this.animatorResult.onframe = (value) => {
      this.model.setRotationAngle(value);
      this.model.invalidate();
    };

    this.animatorResult.onfinish = () => {
      let finalRotation = end % 360;
      let adjustedAngle = finalRotation + 90;
      if (adjustedAngle > 360) {
        adjustedAngle -= 360;
      }
      let index = Math.floor(adjustedAngle / this.onePartAngle);
      this.currentIndex = (this.count - 1) - index;
      this.chooseText = this.parties[this.currentIndex];
      this.isSpinning = false
    };
    this.animatorResult.play();
  }
}
Enter fullscreen mode Exit fullscreen mode

Test Results

img

The application was successfully tested on a standard HarmonyOS wearable emulator and a real device. The pie chart renders correctly and fills the screen. Tapping the central button triggers a smooth, decelerating spin animation. The selected topic is accurately identified and displayed after the spin ends, confirming the animation and calculation logic work as expected.

Limitations or Considerations

Not applicable to apps targeting API Level < 9.

Written by Muhammet Ali Ilgaz

Top comments (0)