👩‍⚕️

fl_chart PieChartで健康管理グラフぽいのを作る 

2024/06/13に公開1

対象者

  • fl_chartを使ったことがある
  • 健康管理アプリぽいのを作りたい
  • Firestoreと組み合わせたい

プロジェクトの説明

Flutterで、健康管理アプリを作ってみたいと思った。今回だと、特定のユーザーの健康状態を保存したデータを使って、円グラフに描画するのをやってみました。

人気のあるグラフパッケージであるfl_chartを使ってみましょう。

  1. Flutterのプロジェクトを作成して、必要なパッケージを追加しておいてください。

https://pub.dev/packages/firebase_core
https://pub.dev/packages/cloud_firestore
https://pub.dev/packages/fl_chart

  1. healtchコレクションを作成します。データ構造はこのようになっております。numberになってますが、Flutter側では、dobule型に変換します。Dartだと、int型で扱いますが。
    [データ構造に合わせてモデルクラスを作成]
import 'package:cloud_firestore/cloud_firestore.dart';

/// [健康状態のモデル]
class Health {
  int? diet; // 食事
  int? sleep; // 睡眠
  int? exercise; // 運動
  int? mentalState; // 精神状態
  Timestamp? createdAt; // 作成日時

  Health({
    this.diet,
    this.sleep,
    this.exercise,
    this.mentalState,
    this.createdAt,
  });
  // JSONからHealthを生成
  factory Health.fromJson(Map<String, dynamic> json) {
    return Health(
      diet: json['diet'],
      sleep: json['sleep'],
      exercise: json['exercise'],
      mentalState: json['mentalState'],
      createdAt: json['createdAt'],
    );
  }
  // HealthをJSONに変換
  Map<String, dynamic> toJson() {
    return {
      'diet': diet,
      'sleep': sleep,
      'exercise': exercise,
      'mentalState': mentalState,
      'createdAt': createdAt,
    };
  }
}
  1. UI側に描画するロジックを作成します。今回は、ログインしてないので、ドキュメントIDはハードコーディングしてます。
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_table_calendart/model/health.dart';

class MyPieChart extends StatefulWidget {
  const MyPieChart({super.key});

  
  _MyPieChartState createState() => _MyPieChartState();
}

class _MyPieChartState extends State<MyPieChart> {
  FirebaseFirestore db = FirebaseFirestore.instance;
  Health? healthData;

  Future<Health> getHealthData() async {
    try {
      final data =
          await db.collection('health').doc('Mhej1ZmSLOf8YvTtrLHy').get();
      return Health.fromJson(data.data()!);
    } on Exception catch (e) {
      throw Exception('Failed to load health data: $e');
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: FutureBuilder<Health>(
        future: getHealthData(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          }
          healthData = snapshot.data;
          List<PieChartSectionData> sections = [
            PieChartSectionData(
              color: Colors.red,
              value: healthData!.diet!.toDouble(),
              title: '食事',
            ),
            PieChartSectionData(
              color: Colors.blue,
              value: healthData!.sleep!.toDouble(),
              title: '睡眠',
            ),
            PieChartSectionData(
              color: Colors.yellow,
              value: healthData!.exercise!.toDouble(),
              title: '運動',
            ),
            PieChartSectionData(
              color: Colors.green,
              value: healthData!.mentalState!.toDouble(),
              title: '精神状態',
            ),
          ];
          return PieChart(
            PieChartData(
              sections: sections,
              sectionsSpace: 3,
              centerSpaceRadius: 100,
              borderData: FlBorderData(show: false),
            ),
          );
        },
      ),
    );
  }
}

今回は、円グラフを使いたいので、PieChartを使用します。

  1. MyPieChartクラスをmain.dartで、インポートすればグラフが表示されるはずです。
main.dart
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';

import 'event_example/line_chart.dart';
import 'firebase_options.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
          useMaterial3: true,
        ),
        home: const MyPieChart());
  }
}

[こんな感じです]

感想

どうでしょうか?
円グラフを使えば健康状態のグラフを作ることができました🥳
他にも使い道はありそう。色々試してみてください。

Discussion

JboyHashimotoJboyHashimoto

棒グラフの場合

こんな感じで作れます。

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_table_calendart/model/health.dart';

class BarChartExample extends StatefulWidget {
  const BarChartExample({super.key});

  
  State<BarChartExample> createState() => _BarChartExampleState();
}

class _BarChartExampleState extends State<BarChartExample> {
  FirebaseFirestore db = FirebaseFirestore.instance;
  Health? healthData;

  Future<Health> getHealthData() async {
    try {
      final data =
          await db.collection('health').doc('Mhej1ZmSLOf8YvTtrLHy').get();
      return Health.fromJson(data.data()!);
    } on Exception catch (e) {
      throw Exception('Failed to load health data: $e');
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Bar Chart Example'),
      ),
      body: FutureBuilder<Health>(
        future: getHealthData(),
        builder: (BuildContext context, AsyncSnapshot<Health> snapshot) {
          if (snapshot.hasError) {
            return const Text("Something went wrong");
          }

          if (snapshot.hasData == false) {
            return const Text("Document does not exist");
          }

          if (snapshot.connectionState == ConnectionState.done) {
            healthData = snapshot.data;
            return Column(
              children: [
                const SizedBox(height: 30.0),
                Expanded(
                  child: BarChart(
                    BarChartData(
                      borderData: FlBorderData(
                          border: const Border(
                        top: BorderSide.none,
                        right: BorderSide.none,
                        left: BorderSide(width: 1),
                        bottom: BorderSide(width: 1),
                      )),
                      groupsSpace: 10,
                      barGroups: [
                        BarChartGroupData(x: 1, barRods: [
                          BarChartRodData(
                              fromY: 0,
                              toY: healthData!.diet!.toDouble(),
                              width: 15,
                              color: Colors.amber),
                        ]),
                        BarChartGroupData(x: 2, barRods: [
                          BarChartRodData(
                              fromY: 0,
                              toY: healthData!.sleep!.toDouble(),
                              width: 15,
                              color: Colors.indigo),
                        ]),
                        BarChartGroupData(x: 3, barRods: [
                          BarChartRodData(
                              fromY: 0,
                              toY: healthData!.exercise!.toDouble(),
                              width: 15,
                              color: Colors.orange),
                        ]),
                        BarChartGroupData(x: 4, barRods: [
                          BarChartRodData(
                              fromY: 0,
                              toY: healthData!.mentalState!.toDouble(),
                              width: 15,
                              color: Colors.green),
                        ]),
                      ],
                    ),
                  ),
                ),
                //  健康の説明を表示
                const Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    Text('食事'),
                    Text('睡眠'),
                    Text('運動'),
                    Text('精神状態'),
                  ],
                ),
                const SizedBox(height: 30.0),
              ],
            );
          }
          return const Text("loading");
        },
      ),
    );
  }
}