syncfusion / flutter-widgets

Syncfusion Flutter widgets libraries include high quality UI widgets and file-format packages to help you create rich, high-quality applications for iOS, Android, and web from a single code base.
1.6k stars 787 forks source link

Question / Need help #2136

Open Welnnys opened 1 month ago

Welnnys commented 1 month ago

Is there a way to reverse the order of the labels on the right side without reversing the order of the circles and if possible to also keep the default legend icons (those little clickable circles)?

// overview_model.dart

import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:intl/intl.dart';

class OverviewModel { final String userUID;

OverviewModel(this.userUID);

Future<List> fetchActivityCounts() async { final now = DateTime.now(); final currentMonthYear = DateFormat('MM-yyyy').format(now);

final querySnapshot = await FirebaseFirestore.instance
    .collection('users')
    .doc(userUID)
    .collection(currentMonthYear)
    .where('ActivityName', isNotEqualTo: null)
    .where('ActivityName', isNotEqualTo: 'placeholder')
    .get();

if (querySnapshot.docs.isEmpty) {
  return generateExampleChartData();
}

Map<String, int> activityCounts = {};
for (var doc in querySnapshot.docs) {
  final activityName = doc['ActivityName'] as String;
  activityCounts[activityName] = (activityCounts[activityName] ?? 0) + 1;
}

List<ChartData> chartData = [];
activityCounts.forEach((activity, count) {
  final formattedTime = '$count $activity records';
  chartData.add(ChartData(activity, count.toDouble(), formattedTime));
});

chartData.sort((a, b) => b.value.compareTo(a.value));
chartData = chartData.take(5).toList();
chartData = chartData.reversed.toList();

return chartData;

}

Future<List> fetchTotalActivityTime() async { final now = DateTime.now(); final currentMonthYear = DateFormat('MM-yyyy').format(now);

final querySnapshot = await FirebaseFirestore.instance
    .collection('users')
    .doc(userUID)
    .collection(currentMonthYear)
    .where('ActivityName', isNotEqualTo: null)
    .where('ActivityName', isNotEqualTo: 'placeholder')
    .get();

if (querySnapshot.docs.isEmpty) {
  return generateExampleChartData();
}

Map<String, double> activityTimes = {};
for (var doc in querySnapshot.docs) {
  final activityName = doc['ActivityName'] as String;
  final int activityTime = doc['ActivityTime'] as int;
  double activityTimeInHours = activityTime / 60.0;

  activityTimes[activityName] =
      (activityTimes[activityName] ?? 0) + activityTimeInHours;
}

List<ChartData> chartData = [];
activityTimes.forEach((activity, time) {
  final hours = time.floor();
  final minutes = ((time - hours) * 60).toInt();
  final formattedTime = '$hours hours $minutes minutes';
  chartData.add(ChartData(activity, time, formattedTime));
});

chartData.sort((a, b) => b.value.compareTo(a.value));
chartData = chartData.take(5).toList();
chartData = chartData.reversed.toList();

return chartData;

}

Future<List<Map<String, dynamic>>> fetchPersonalBestActivities( List sortedActivityTypes) async { List allActivityTypes = [ 'Running', 'Cycling', 'Walking', 'Swimming', 'Volleyball', 'Bouldering', 'Football', 'Basketball', 'Fitness', ];

List<Map<String, dynamic>> allActivities = [];
for (String activityType in allActivityTypes) {
  final querySnapshot = await FirebaseFirestore.instance
      .collection('users')
      .doc(userUID)
      .collection(activityType)
      .orderBy('steps', descending: true)
      .limit(1)
      .get();

  if (querySnapshot.docs.isNotEmpty) {
    final bestActivity = querySnapshot.docs.first.data();
    bestActivity['docId'] = querySnapshot.docs.first.id;
    bestActivity['activityType'] = activityType;
    allActivities.add(bestActivity);
  }
}

List<Map<String, dynamic>> chartActivities = [];
List<Map<String, dynamic>> nonChartActivities = [];

for (var activity in allActivities) {
  if (sortedActivityTypes.contains(activity['activityType'])) {
    chartActivities.add(activity);
  } else {
    nonChartActivities.add(activity);
  }
}

chartActivities.sort((a, b) {
  String typeA = a['activityType'];
  String typeB = b['activityType'];
  return sortedActivityTypes
      .indexOf(typeA)
      .compareTo(sortedActivityTypes.indexOf(typeB));
});

chartActivities = chartActivities.reversed.toList();

return [...chartActivities, ...nonChartActivities];

}

List generateExampleChartData() { List exampleData = [ ChartData('Example 1', 3, 'No Data'), ChartData('Example 2', 5, 'No Data'), ChartData('Example 3', 10, 'No Data'), ChartData('Example 4', 15, 'No Data'), ChartData('Example 5', 20, 'No Data'), ];

return exampleData;

} }

class ChartData { ChartData(this.category, this.value, this.formattedTime); final String category; final double value; final String formattedTime; }


// overview_page.dart

import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; import 'package:sporti_angel_kolev/models/overview_model.dart'; import 'package:sporti_angel_kolev/controllers/overview_controller.dart'; import 'package:sporti_angel_kolev/common/activity_list_widget.dart'; import 'package:sporti_angel_kolev/common/profile_picture.dart'; import 'package:sporti_angel_kolev/common/user_information.dart';

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

@override _OverviewPageState createState() => _OverviewPageState(); }

class _OverviewPageState extends State { bool showTotalActivityTime = true; late OverviewController _controller;

@override void initState() { super.initState(); final userUID = UserInformation().userUID; _controller = OverviewController(OverviewModel(userUID)); }

@override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar( surfaceTintColor: Colors.white, title: const Text( 'Overview', style: TextStyle( fontWeight: FontWeight.bold, ), ), centerTitle: true, automaticallyImplyLeading: false, backgroundColor: Colors.white, actions: const [ProfilePicture()], ), body: Center( child: Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: ElevatedButton( onPressed: () { setState(() { showTotalActivityTime = !showTotalActivityTime; }); }, style: ElevatedButton.styleFrom( foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 15.0, horizontal: 30.0), backgroundColor: Colors.redAccent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(30.0), ), elevation: 5, shadowColor: Colors.black, ), child: Text( showTotalActivityTime ? 'Monthly Activity Count' : 'Monthly Total Activity Time', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), Expanded( child: Column( children: [ Flexible( child: FutureBuilder<List>( future: showTotalActivityTime ? _controller.fetchTotalActivityTime() : _controller.fetchActivityCounts(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center( child: CircularProgressIndicator( valueColor: AlwaysStoppedAnimation( Colors.red))); } else if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else { final chartData = snapshot.data ?? [];

                      List<String> sortedActivityTypes =
                          chartData.map((data) => data.category).toList();

                      return Column(
                        children: [
                          SfCircularChart(
                            centerX: '130',
                            legend: const Legend(
                                isVisible: true,
                                position: LegendPosition.right),
                            series: <CircularSeries>[
                              RadialBarSeries<ChartData, String>(
                                dataSource: chartData,
                                xValueMapper: (ChartData data, _) =>
                                    data.category,
                                yValueMapper: (ChartData data, _) =>
                                    data.value,
                                dataLabelMapper: (ChartData data, _) =>
                                    data.formattedTime,
                                dataLabelSettings: const DataLabelSettings(
                                    isVisible: true),
                                trackBorderWidth: 2,
                                gap: '17%',
                                innerRadius: '10%',
                              ),
                            ],
                          ),
                          Expanded(
                            child: SingleChildScrollView(
                              child: ActivityListWidget(
                                fetchActivities: () =>
                                    _controller.fetchPersonalBestActivities(
                                        sortedActivityTypes),
                                onActivityDeleted: () {
                                  setState(() {});
                                },
                                centerEmptyMessage: false,
                              ),
                            ),
                          ),
                        ],
                      );
                    }
                  },
                ),
              ),
            ],
          ),
        ),
      ],
    ),
  ),
);

} }

image

Welnnys commented 4 weeks ago

Can I please get a response :/?

Welnnys commented 3 weeks ago

Hello :/, it's been 3 weeks

ghost commented 2 weeks ago

Hi @Welnnys,

We have analyzed you query and you can achieve your requirement by using LegendItemBuilder. However by reversing the text alone in legend does not reflect in chart segment when click on respective legend item. We have shared code snippet and sample for your reference.

Code Snippet:

@override
  Widget build(BuildContext context) {
    // Reverse the chart data only for the legend
    List<_ChartData> reversedChartData = List.from(chartData.reversed);

    return Scaffold(
      body: SfCircularChart(
        ……………….
          // Customizing the legend to show in reversed order
         legendItemBuilder: (String name, dynamic series, dynamic point, int index) {
            final ChartPoint _point = point as ChartPoint; 
            // Define colors for each category in reversed order
            final Color color = index == 0
                ? Colors.blue
                : index == 1
                    ? Colors.green
                    : Colors.red;

            return Container(
              padding: const EdgeInsets.all(4),
              child: Row(
                children: [
                  // Circular shape icon with custom color
                  Container(
                    width: 10,
                    height: 10,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      color: color,
                    ),
                  ),
                  const SizedBox(width: 8),
                  // Display the reversed label
                  Text(reversedChartData[index].x),
                ],
              ),
            );
          },
…………….

Screenshot: image

For more information, kindly refer to the following UG, UG link: https://help.syncfusion.com/flutter/circular-charts/legend#legend-item-template

Sample : gh_2136.zip

Regards, Thilip Chandru.

Welnnys commented 2 weeks ago

And there is no way to make it reflect the chart?

Welnnys commented 2 weeks ago

and to have the default legend icons aswell

Welnnys commented 2 weeks ago

I have already responded, I am waiting for you to respond