From 5ac7afe1f103a520b35d8ad824f65c6867d4f8c1 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Fri, 10 Jan 2025 10:17:53 +0530 Subject: [PATCH] cahrt code merged --- lib/config/my_router.dart | 12 +- .../Bottom Navigation Pages/uae_numbers.dart | 198 +++--- lib/presentation/Screens/charts/chart.dart | 258 ++++--- .../charts/filters/search_filter_helper.dart | 33 + .../Screens/charts/models/chart_data.dart | 13 + .../Screens/charts/screens/chart_screen.dart | 600 +++++++++++++++++ .../Screens/charts/services/api_service.dart | 184 +++++ .../Screens/charts/widgets/chart_widget.dart | 632 ++++++++++++++++++ .../tab_routes/home_route.dart | 6 +- .../drawer_routes/custom_drawer_routes.dart | 4 +- pubspec.lock | 8 + pubspec.yaml | 1 + 12 files changed, 1732 insertions(+), 217 deletions(-) create mode 100644 lib/presentation/Screens/charts/filters/search_filter_helper.dart create mode 100644 lib/presentation/Screens/charts/models/chart_data.dart create mode 100644 lib/presentation/Screens/charts/screens/chart_screen.dart create mode 100644 lib/presentation/Screens/charts/services/api_service.dart create mode 100644 lib/presentation/Screens/charts/widgets/chart_widget.dart diff --git a/lib/config/my_router.dart b/lib/config/my_router.dart index c200ac19..6e2daf40 100644 --- a/lib/config/my_router.dart +++ b/lib/config/my_router.dart @@ -298,6 +298,8 @@ import 'package:uae_stat/presentation/Screens/Bottom%20Navigation%20Pages/compet import 'package:uae_stat/presentation/Screens/Bottom%20Navigation%20Pages/country_profile.dart'; import 'package:uae_stat/presentation/Screens/Bottom%20Navigation%20Pages/uae_numbers.dart'; import 'package:uae_stat/presentation/Screens/auth_verification/registration.dart'; +import 'package:uae_stat/presentation/Screens/charts/chart.dart'; +import 'package:uae_stat/presentation/Screens/charts/screens/chart_screen.dart'; import 'package:uae_stat/presentation/Screens/demo_home2.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/Drawer%20Items/user_guide/getting_started.dart'; import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; @@ -307,7 +309,6 @@ import '../presentation/Screens/auth_verification/changepassword.dart'; import '../presentation/Screens/auth_verification/confirm_password.dart'; import '../presentation/Screens/auth_verification/create_new_pw.dart'; import '../presentation/Screens/auth_verification/otp_verification.dart'; -import '../presentation/Screens/charts/chart.dart'; import '../presentation/Screens/demo_home.dart'; import '../presentation/Screens/profilepage.dart'; import '../presentation/routes/auth_routes/login_route.dart'; @@ -355,6 +356,15 @@ final GoRouter router = GoRouter( return ChartScreen(dataSets: dataSets); }, ), + GoRoute( + path: '/chartScreen/:dataSets', + builder: (context, state) { + final dataSets = state.pathParameters['dataSets']!; + print('Router $dataSets'); + // return ChartScreen(dataSets: dataSets); + return ChartScreen1(dataSets: dataSets); + }, + ), GoRoute( path: '/mailverification', builder: (context, state) => EmailVerificationScreen( diff --git a/lib/presentation/Screens/Bottom Navigation Pages/uae_numbers.dart b/lib/presentation/Screens/Bottom Navigation Pages/uae_numbers.dart index b1899ffa..6787e132 100644 --- a/lib/presentation/Screens/Bottom Navigation Pages/uae_numbers.dart +++ b/lib/presentation/Screens/Bottom Navigation Pages/uae_numbers.dart @@ -3,7 +3,6 @@ import 'package:uae_stat/presentation/routes/bottom_bar_routes/tab_routes/home_r import 'package:uae_stat/presentation/routes/drawer_routes/custom_drawer_routes.dart'; import 'package:uae_stat/presentation/components/constant/constant.dart'; - class UaeNumbers extends StatelessWidget { const UaeNumbers({super.key}); @@ -16,7 +15,7 @@ class UaeNumbers extends StatelessWidget { body: //EconomicDashboard(), //EconomyExpandTile() - uaenumberWidget()); + uaenumberWidget()); } } @@ -31,44 +30,82 @@ class uaenumberWidget extends StatelessWidget { padding: const EdgeInsets.all(16.0), child: ListView( children: [ - Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(30.0), - border: Border.all(width: 1) - - ), - child: TextField( - decoration: InputDecoration( - hintText: "Search", - prefixIcon: Icon(Icons.search), - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0), - ), - ), - ), - SizedBox(height: myheight/35,), + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(30.0), + border: Border.all(width: 1)), + child: TextField( + decoration: InputDecoration( + hintText: "Search", + prefixIcon: Icon(Icons.search), + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(vertical: 15.0, horizontal: 20.0), + ), + ), + ), + SizedBox( + height: myheight / 35, + ), CustomExpandableTile( title: 'ECONOMY', titleBackgroundColor: economyColor, children: [ - buildCategoryTitle('National Accounts',economyColor), + buildCategoryTitle('National Accounts', economyColor), buildCardRow([ - buildCard(context,'GDP (Constant)', '1.62T', '2022 (AED)',economyColor,economyColor,mywidth/2.5), - buildCard(context,'FDI', '1.45T', '2021 (AED)',economyColor,economyColor,mywidth/2.5), + buildCard(context, 'GDP (Constant)', '1.62T', '2022 (AED)', + economyColor, economyColor, mywidth / 2.5), + buildCard(context, 'FDI', '1.45T', '2021 (AED)', economyColor, + economyColor, mywidth / 2.5), ]), - buildCategoryTitle('International Trade',economyColor), + buildCategoryTitle('International Trade', economyColor), buildCardRow([ - buildCard(context,'Total Trade', '669.9B', 'Jan - Mar 2024 (AED)',economyColor,economyColor,mywidth/2.5), - buildCard(context,'Total Import', '686B', 'Jan - Mar 2024 (AED)',economyColor,economyColor,mywidth/2.5), + buildCard( + context, + 'Total Trade', + '669.9B', + 'Jan - Mar 2024 (AED)', + economyColor, + economyColor, + mywidth / 2.5), + buildCard( + context, + 'Total Import', + '686B', + 'Jan - Mar 2024 (AED)', + economyColor, + economyColor, + mywidth / 2.5), ]), buildCardRow([ - buildCard(context,'Total Export', '669.9B', 'Jan - Mar 2024 (AED)',economyColor,economyColor,mywidth/2.5), - buildCard(context,'Total ReExport', '686B', 'Jan - Mar 2024 (AED)',economyColor,economyColor,mywidth/2.5), + buildCard( + context, + 'Total Export', + '669.9B', + 'Jan - Mar 2024 (AED)', + economyColor, + economyColor, + mywidth / 2.5), + buildCard( + context, + 'Total ReExport', + '686B', + 'Jan - Mar 2024 (AED)', + economyColor, + economyColor, + mywidth / 2.5), ]), - buildCategoryTitle('Prices',economyColor), + buildCategoryTitle('Prices', economyColor), buildCardRow([ - buildCard(context,'Total Export', '669.9B', 'Jan - Mar 2024 (AED)',economyColor,economyColor,mywidth/1.2), + buildCard( + context, + 'Total Export', + '669.9B', + 'Jan - Mar 2024 (AED)', + economyColor, + economyColor, + mywidth / 1.2), ]), ], ), @@ -76,25 +113,35 @@ class uaenumberWidget extends StatelessWidget { title: 'SOCIAL', titleBackgroundColor: socialColor, children: [ - buildCategoryTitle('Population',socialColor), + buildCategoryTitle('Population', socialColor), buildCardRow([ - InkWell(onTap: (){},child: buildCard(context,'Population', '9.89M', '2024',socialColor,socialColor,mywidth/2.5)), - buildCard(context,'Population Growth', '2.1%', '2023',socialColor,socialColor,mywidth/2.5), + InkWell( + onTap: () {}, + child: buildCard(context, 'Population', '9.89M', '2024', + socialColor, socialColor, mywidth / 2.5)), + buildCard(context, 'Population Growth', '2.1%', '2023', + socialColor, socialColor, mywidth / 2.5), ]), - buildCategoryTitle('Vital Statistics',socialColor), + buildCategoryTitle('Vital Statistics', socialColor), buildCardRow([ - buildCard(context,'Marriages', '96%', '2023',socialColor,socialColor,mywidth/2.5), - buildCard(context,'Divorces', '1.2K', '2024',socialColor,socialColor,mywidth/2.5), + buildCard(context, 'Marriages', '96%', '2023', socialColor, + socialColor, mywidth / 2.5), + buildCard(context, 'Divorces', '1.2K', '2024', socialColor, + socialColor, mywidth / 2.5), ]), - buildCategoryTitle('Education',socialColor), + buildCategoryTitle('Education', socialColor), buildCardRow([ - buildCard(context,'General Education', '96%', '2023',socialColor,socialColor,mywidth/2.5), - buildCard(context,'Higher Education', '1.2K', '2024',socialColor,socialColor,mywidth/2.5), + buildCard(context, 'General Education', '96%', '2023', + socialColor, socialColor, mywidth / 2.5), + buildCard(context, 'Higher Education', '1.2K', '2024', + socialColor, socialColor, mywidth / 2.5), ]), - buildCategoryTitle('Health',socialColor), + buildCategoryTitle('Health', socialColor), buildCardRow([ - buildCard(context,'Hospital', '96%', '2023',socialColor,socialColor,mywidth/2.5), - buildCard(context,'Clinic and Centres', '1.2K', '2024',socialColor,socialColor,mywidth/2.5), + buildCard(context, 'Hospital', '96%', '2023', socialColor, + socialColor, mywidth / 2.5), + buildCard(context, 'Clinic and Centres', '1.2K', '2024', + socialColor, socialColor, mywidth / 2.5), ]), ], ), @@ -102,24 +149,32 @@ class uaenumberWidget extends StatelessWidget { title: 'ENVIRONMENT', titleBackgroundColor: environmentColor, children: [ - buildCategoryTitle('Agriculture',environmentColor), + buildCategoryTitle('Agriculture', environmentColor), buildCardRow([ - buildCard(context,'Crops - Total Area', '27°C', 'Average 2024',environmentColor,environmentColor,mywidth/2.5), - buildCard(context,'Livestock', '120mm', '2024',environmentColor,environmentColor,mywidth/2.5), + buildCard(context, 'Crops - Total Area', '27°C', 'Average 2024', + environmentColor, environmentColor, mywidth / 2.5), + buildCard(context, 'Livestock', '120mm', '2024', + environmentColor, environmentColor, mywidth / 2.5), ]), - buildCategoryTitle('Environment',environmentColor), + buildCategoryTitle('Environment', environmentColor), buildCardRow([ - buildCard(context,'Climate - Max Temp', '15%', '2024',environmentColor,environmentColor,mywidth/2.5), - buildCard(context,'Climate - Min Temp', '30%', '2023',environmentColor,environmentColor,mywidth/2.5), + buildCard(context, 'Climate - Max Temp', '15%', '2024', + environmentColor, environmentColor, mywidth / 2.5), + buildCard(context, 'Climate - Min Temp', '30%', '2023', + environmentColor, environmentColor, mywidth / 2.5), ]), buildCardRow([ - buildCard(context,'Desalinated Produced Water', '15%', '2024',environmentColor,environmentColor,mywidth/2.5), - buildCard(context,'Area of Natural Reserves', '30%', '2023',environmentColor,environmentColor,mywidth/2.5), + buildCard(context, 'Desalinated Produced Water', '15%', '2024', + environmentColor, environmentColor, mywidth / 2.5), + buildCard(context, 'Area of Natural Reserves', '30%', '2023', + environmentColor, environmentColor, mywidth / 2.5), ]), - buildCategoryTitle('Energy',environmentColor), + buildCategoryTitle('Energy', environmentColor), buildCardRow([ - buildCard(context,'Electricity Production', '15%', '2024',environmentColor,environmentColor,mywidth/2.5), - buildCard(context,'Renewable Energy Production', '30%', '2023',environmentColor,environmentColor,mywidth/2.5), + buildCard(context, 'Electricity Production', '15%', '2024', + environmentColor, environmentColor, mywidth / 2.5), + buildCard(context, 'Renewable Energy Production', '30%', '2023', + environmentColor, environmentColor, mywidth / 2.5), ]), ], ), @@ -128,7 +183,7 @@ class uaenumberWidget extends StatelessWidget { ); } - Widget buildCategoryTitle(String title,Color color) { + Widget buildCategoryTitle(String title, Color color) { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Text( @@ -156,7 +211,8 @@ class uaenumberWidget extends StatelessWidget { ); } - Widget buildCard(BuildContext context,String title, String value, String subtitle,Color bordercolor,boldColor,double mywidth) { + Widget buildCard(BuildContext context, String title, String value, + String subtitle, Color bordercolor, boldColor, double mywidth) { // double myheight = MediaQuery.of(context).size.height; // double mywidth = MediaQuery.of(context).size.width; return Card( @@ -181,11 +237,7 @@ class uaenumberWidget extends StatelessWidget { style: robotoRegular11, ), SizedBox(height: 8), - Text( - subtitle, - textAlign: TextAlign.center, - style: subtitleStyle - ), + Text(subtitle, textAlign: TextAlign.center, style: subtitleStyle), SizedBox(height: 8), Text( value, @@ -236,8 +288,7 @@ class _CustomExpandableTileState extends State { child: Container( decoration: BoxDecoration( color: widget.titleBackgroundColor, - borderRadius: BorderRadius.all(Radius.circular(12)) - ), + borderRadius: BorderRadius.all(Radius.circular(12))), padding: const EdgeInsets.all(12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -268,18 +319,17 @@ class _CustomExpandableTileState extends State { height: isExpanded ? myheight : 0, child: isExpanded ? SingleChildScrollView( - child: Container( - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(20)) - ), - - padding: const EdgeInsets.all(10), - child: Column( - children: widget.children, - ), - ), - ) + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: + BorderRadius.all(Radius.circular(20))), + padding: const EdgeInsets.all(10), + child: Column( + children: widget.children, + ), + ), + ) : null, ), ), @@ -288,5 +338,3 @@ class _CustomExpandableTileState extends State { ); } } - - diff --git a/lib/presentation/Screens/charts/chart.dart b/lib/presentation/Screens/charts/chart.dart index 1911ddee..a34bf4a4 100644 --- a/lib/presentation/Screens/charts/chart.dart +++ b/lib/presentation/Screens/charts/chart.dart @@ -1035,191 +1035,177 @@ class ChartData { // [ // { -// chart_type :"scatter" -// dataset :"divorces" -// is_chart :"true" -// kpi:"divorces" -// filters:[ -// { -// "filter_key": "REF_AREA", -// "filter_data": [ -// "AE", -// "AE-AJ", -// "AE-RK", -// "AE-FJ", -// "AE-AZ", -// "AE-DU", -// "AE-SH", -// "AE-UQ" -// ] -// }, -// { -// "filter_key": "TIME_PERIOD", -// "filter_data": [ -// "2021", -// "2017", -// "2018", -// "2019", -// "2020" -// ] -// }, -// { -// "filter_key": "DV_TYPE", -// "filter_data": [ -// "D_EM_NEF", -// "D_EM_EF", -// "D_NEW_NEF", -// "D_TOT", -// "D_NEM_EF" -// ] -// } -// ] -// group_by:"DV_TYPE" -// main_id :"social" +// chart_heading :"Guest Nights by Region" +// chart_type :"fl_stacked_bar" +// dataset :"hotels" +// group_by :"GUEST_REGION" +// is_chart :"true" +// kpi :"hotel_occupancy_rate_chart_1" +// main_id :"economy" // response:[ // { // "ObsKey": { -// "DV_TYPE": "D_TOT", // "FREQ": "A", -// "MEASURE": "D", -// "REF_AREA": "AE-DU", -// "SOURCE_DETAIL": "FCSAMD", -// "TIME_PERIOD": "2017", +// "GUEST_REGION": "AF", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSC", +// "TIME_PERIOD": "2016", // "UNIT_MEASURE": "NUMBER" // }, // "ObsValue": { -// "Value": "1484" +// "Value": "2659446" // } // }, // { // "ObsKey": { -// "DV_TYPE": "D_TOT", // "FREQ": "A", -// "MEASURE": "D", -// "REF_AREA": "AE-DU", -// "SOURCE_DETAIL": "FCSAMD", +// "GUEST_REGION": "AF", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSC", +// "TIME_PERIOD": "2017", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "2721363" +// } +// }, +// { +// "ObsKey": { +// "FREQ": "A", +// "GUEST_REGION": "ASC", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSC", +// "TIME_PERIOD": "2016", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "16454601" +// } +// }, +// { +// "ObsKey": { +// "FREQ": "A", +// "GUEST_REGION": "ASC", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSC", +// "TIME_PERIOD": "2017", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "18960773" +// } +// }, +// { +// "ObsKey": { +// "FREQ": "A", +// "GUEST_REGION": "UAE", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSC", // "TIME_PERIOD": "2019", // "UNIT_MEASURE": "NUMBER" // }, // "ObsValue": { -// "Value": "1308" +// "Value": "14286573" // } // }, // { // "ObsKey": { -// "DV_TYPE": "D_TOT", // "FREQ": "A", -// "MEASURE": "D", -// "REF_AREA": "AE-DU", -// "SOURCE_DETAIL": "FCSAMD", -// "TIME_PERIOD": "2017", -// "UNIT_MEASURE": "NUMBER" -// }, -// "ObsValue": { -// "Value": "1484" -// } -// }, -// { -// "ObsKey": { -// "DV_TYPE": "D_TOT", -// "FREQ": "A", -// "MEASURE": "D", -// "REF_AREA": "AE-DU", -// "SOURCE_DETAIL": "FCSAMD", -// "TIME_PERIOD": "2018", -// "UNIT_MEASURE": "NUMBER" -// }, -// "ObsValue": { -// "Value": "1321" -// } -// }, -// { -// "ObsKey": { -// "DV_TYPE": "D_NEM_EF", -// "FREQ": "A", -// "MEASURE": "D", +// "GUEST_REGION": "UAE", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", // "REF_AREA": "AE", -// "SOURCE_DETAIL": "FCSAMD", -// "TIME_PERIOD": "2017", -// "UNIT_MEASURE": "NUMBER" -// }, -// "ObsValue": { -// "Value": "181" -// } -// }, -// { -// "ObsKey": { -// "DV_TYPE": "D_NEM_EF", -// "FREQ": "A", -// "MEASURE": "D", -// "REF_AREA": "AE", -// "SOURCE_DETAIL": "FCSAMD", -// "TIME_PERIOD": "2019", -// "UNIT_MEASURE": "NUMBER" -// }, -// "ObsValue": { -// "Value": "182" -// } -// }, -// { -// "ObsKey": { -// "DV_TYPE": "D_EM_NEF", -// "FREQ": "A", -// "MEASURE": "D", -// "REF_AREA": "AE", -// "SOURCE_DETAIL": "FCSAMD", -// "TIME_PERIOD": "2017", -// "UNIT_MEASURE": "NUMBER" -// }, -// "ObsValue": { -// "Value": "766" -// } -// }, -// { -// "ObsKey": { -// "DV_TYPE": "D_EM_NEF", -// "FREQ": "A", -// "MEASURE": "D", -// "REF_AREA": "AE", -// "SOURCE_DETAIL": "FCSAMD", +// "SOURCE_DETAIL": "FCSC", // "TIME_PERIOD": "2020", // "UNIT_MEASURE": "NUMBER" // }, // "ObsValue": { -// "Value": "618" +// "Value": "13982919" // } // }, // { // "ObsKey": { -// "DV_TYPE": "D_EM_EF", // "FREQ": "A", -// "MEASURE": "D", +// "GUEST_REGION": "EC", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", // "REF_AREA": "AE", -// "SOURCE_DETAIL": "FCSAMD", +// "SOURCE_DETAIL": "FCSC", +// "TIME_PERIOD": "2019", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "19917654" +// } +// }, +// { +// "ObsKey": { +// "FREQ": "A", +// "GUEST_REGION": "EC", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSC", +// "TIME_PERIOD": "2020", +// "UNIT_MEASURE": "NUMBER" +// }, +// "ObsValue": { +// "Value": "10041664" +// } +// }, +// { +// "ObsKey": { +// "FREQ": "A", +// "GUEST_REGION": "OC", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", +// "REF_AREA": "AE", +// "SOURCE_DETAIL": "FCSC", // "TIME_PERIOD": "2017", // "UNIT_MEASURE": "NUMBER" // }, // "ObsValue": { -// "Value": "1717" +// "Value": "1260359" // } // }, // { // "ObsKey": { -// "DV_TYPE": "D_EM_EF", // "FREQ": "A", -// "MEASURE": "D", +// "GUEST_REGION": "OC", +// "H_INDICATOR": "GUN", +// "H_TYPE": "_Z", +// "MEASURE": "H", // "REF_AREA": "AE", -// "SOURCE_DETAIL": "FCSAMD", +// "SOURCE_DETAIL": "FCSC", // "TIME_PERIOD": "2018", // "UNIT_MEASURE": "NUMBER" // }, // "ObsValue": { -// "Value": "1720" +// "Value": "1127282" // } // } // // ] -// sub_id :"vital_statistics" -// url :"https://releaseeuaestat.fcsc.gov.ae/rest/data/FCSA,DF_DV_NA,1.5.0/.A....?startPeriod=2017&endPeriod=2024&dimensionAtObservation=AllDimensions" +// sub_id :"tourism" +// url :"https://releaseeuaestat.fcsc.gov.ae/rest/data/FCSA,DF_GUEST_REGION,4.3.0/...A..GUN.OTH+OC+AF+EC+AM+AC+ASC+GCC+UAE.?startPeriod=2016&dimensionAtObservation=AllDimensions" // } // ] diff --git a/lib/presentation/Screens/charts/filters/search_filter_helper.dart b/lib/presentation/Screens/charts/filters/search_filter_helper.dart new file mode 100644 index 00000000..afaa9f75 --- /dev/null +++ b/lib/presentation/Screens/charts/filters/search_filter_helper.dart @@ -0,0 +1,33 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; + +List filterCharts({ + required List apiData, + required Map> filters, +}) { +// Initialize the filtered data as the original dataset + List filteredData = apiData; + +// Apply filtering for each filter key dynamically + for (var filterKey in filters.keys) { + List? filterValues = filters[filterKey]; + if (filterValues != null && filterValues.isNotEmpty) { + // Filter data based on the current filter key + List currentFilteredData = filteredData.where((item) { + var obsKey = item["ObsKey"]; + return filterValues.contains(obsKey[filterKey]); + }).toList(); + + // If current filter excludes all items, return previous filtered data + if (currentFilteredData.isEmpty) { + return filteredData; + } + + // Update filtered data for the next iteration + filteredData = currentFilteredData; + } + } + print('filteredData $filteredData'); + return filteredData; // Return the final filtered data +} diff --git a/lib/presentation/Screens/charts/models/chart_data.dart b/lib/presentation/Screens/charts/models/chart_data.dart new file mode 100644 index 00000000..227d11fc --- /dev/null +++ b/lib/presentation/Screens/charts/models/chart_data.dart @@ -0,0 +1,13 @@ +class ChartData { + final String xLabel; + final double yValue; + + ChartData({required this.xLabel, required this.yValue}); + + factory ChartData.fromJson(Map json) { + return ChartData( + xLabel: json['x_label'] as String, + yValue: (json['y_value'] as num).toDouble(), + ); + } +} diff --git a/lib/presentation/Screens/charts/screens/chart_screen.dart b/lib/presentation/Screens/charts/screens/chart_screen.dart new file mode 100644 index 00000000..0237b800 --- /dev/null +++ b/lib/presentation/Screens/charts/screens/chart_screen.dart @@ -0,0 +1,600 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:uae_stat/presentation/Screens/charts/services/api_service.dart'; +import 'package:uae_stat/presentation/Screens/charts/widgets/chart_widget.dart'; + +import '../filters/search_filter_helper.dart'; + +class ChartScreen1 extends StatefulWidget { + final String dataSets; + const ChartScreen1({Key? key, required this.dataSets}); + @override + _ChartScreen1State createState() => _ChartScreen1State(); +} + +class _ChartScreen1State extends State { + List> selectedFiltersStorage = []; + final GlobalKey _scaffoldKey = GlobalKey(); + final ApiService apiService = ApiService(); + bool isLoading = true; + List isChartData = []; + List nonChartData = []; + List filterData = []; + List chartsData = []; + List cardData = []; + List originalChartsData = []; + List originalCardData = []; + + @override + void initState() { + super.initState(); + fetchChartData(widget.dataSets); + } + + Future fetchChartData(String dataSets) async { + var data = await apiService.fetchChartData(dataSets); + setState(() { + isChartData = data['isChartData'] ?? []; + nonChartData = data['nonChartData'] ?? []; + filterData = data['filterData'] ?? []; + + originalChartsData = List.from(isChartData); // Store original data + originalCardData = List.from(nonChartData); + + chartsData = isChartData; + cardData = nonChartData; + print('chartsData :- $chartsData'); + print('cardData :- $cardData'); + print('filterData :- $filterData'); + isLoading = false; + }); + print(chartsData); + print(cardData); + } + + void applyFilters(BuildContext context, List filters, List data, + List dataCard, List selectedFilters) { + print("Selected Filters before applying: $selectedFilters"); + // Check if all filter_data is empty + if (selectedFilters.every((filter) => filter['filter_data'].isEmpty)) { + setState(() { + chartsData = List.from(originalChartsData); // Restore the original data + cardData = List.from(originalCardData); // Restore the original data + }); + print('Returning as no filters are selected'); + Navigator.pop(context); + return; // Exit early since all filter_data are empty + } + + // Loop through each chart data in the `data` list + List filteredData = []; + for (var chart in data) { + // Extract response data for filtering + List response = chart['response'] ?? []; + + // Filter the response based on selected filters + var chartFilteredData = response.where((responseItem) { + final obsKey = responseItem['ObsKey']; + + // Check if each selected filter's `filter_data` matches `ObsKey` values + return selectedFilters.every((filter) { + final filterKey = filter['filter_key']; + final filterValues = filter['filter_data']; + + // Ensure the filter_key exists in the `ObsKey` and check if it matches any of the `filter_data` + if (obsKey.containsKey(filterKey)) { + final obsKeyValue = obsKey[filterKey]?.toString(); + return filterValues.isEmpty || filterValues.contains(obsKeyValue); + } + return false; + }); + }).toList(); + + // If any data matches the filter, add the whole chart data object + if (chartFilteredData.isNotEmpty) { + filteredData.add({ + ...chart, // Include all other properties of the chart object + 'response': chartFilteredData, // Only include filtered response data + }); + } + } + + List filteredCardData = []; + for (var chart in dataCard) { + Map chartData = Map.from(chart); + // Extract response data for filtering + List response = chartData['response'] ?? []; + + // Filter the response based on selected filters + var cardFilteredData = response.where((responseItem) { + final obsKey = responseItem['ObsKey']; + + // Check if each selected filter's `filter_data` matches `ObsKey` values + return selectedFilters.every((filter) { + final filterKey = filter['filter_key']; + final filterValues = filter['filter_data']; + + // Ensure the filter_key exists in the `ObsKey` and check if it matches any of the `filter_data` + if (obsKey.containsKey(filterKey)) { + final obsKeyValue = obsKey[filterKey]?.toString(); + return filterValues.isEmpty || filterValues.contains(obsKeyValue); + } + return false; + }); + }).toList(); + + // If any data matches the filter, add the whole chart data object + if (cardFilteredData.isNotEmpty) { + filteredCardData.add({ + ...chart, // Include all other properties of the chart object + 'response': cardFilteredData, // Only include filtered response data + }); + } + } + + // Update the chartsData with the filtered data + setState(() { + chartsData = filteredData; + cardData = filteredCardData; + }); + + print("Filtered Data: $filteredData"); + print("Filtered Card: $filteredCardData"); + + // Go back after applying filters + Navigator.pop(context); + } + + void showRightSideModal(BuildContext context, List filters, List data) { + // Initialize selected filters structure from the stored value, if exists + List> selectedFilters = + selectedFiltersStorage.isNotEmpty + ? List.from(selectedFiltersStorage) // Use stored filters + : filters.map((filter) { + return {"filter_key": filter["filter_key"], "filter_data": []}; + }).toList(); // Or initialize empty filters + + showModalBottomSheet( + context: context, + isScrollControlled: true, + backgroundColor: Colors.transparent, + builder: (BuildContext context) { + return Align( + alignment: Alignment.centerRight, + child: Container( + width: MediaQuery.of(context).size.width * 0.7, + height: MediaQuery.of(context).size.height, + color: Colors.white, + child: Column( + children: [ + // Header Section + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Filters', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + IconButton( + icon: Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ], + ), + ), + // Filters Section + Expanded( + child: ListView.builder( + itemCount: filters.length, + itemBuilder: (context, index) { + final filter = filters[index]; + final filterKey = filter["filter_key"]; + final filterData = filter["filter_data"]; + + return StatefulBuilder( + builder: (context, setState) { + // Get the corresponding selected filter object + var selectedFilter = selectedFilters + .firstWhere((f) => f["filter_key"] == filterKey); + + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + filterKey, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + InkWell( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return StatefulBuilder( + builder: (context, dialogSetState) { + return AlertDialog( + title: Text('Select $filterKey'), + content: SingleChildScrollView( + child: ListBody( + children: filterData + .map((value) { + return CheckboxListTile( + title: Text( + value.toString()), + value: selectedFilter[ + "filter_data"] + .contains(value), + onChanged: + (bool? isChecked) { + dialogSetState(() { + if (isChecked == + true) { + selectedFilter[ + "filter_data"] + .add(value); + } else { + selectedFilter[ + "filter_data"] + .remove(value); + } + }); + }, + ); + }).toList(), + ), + ), + actions: [ + TextButton( + onPressed: () { + // Store the updated selected filters after dialog closes + setState(() {}); + Navigator.pop(context); + }, + child: Text('OK'), + ), + ], + ); + }, + ); + }, + ); + }, + child: Container( + width: double.infinity, + padding: EdgeInsets.symmetric( + horizontal: 16.0, vertical: 12.0), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey), + borderRadius: BorderRadius.circular(8.0), + ), + child: Wrap( + spacing: 8.0, + runSpacing: 4.0, + children: + selectedFilter["filter_data"].isEmpty + ? [ + Text('Select $filterKey', + style: TextStyle( + color: Colors.grey)) + ] + : selectedFilter["filter_data"] + .map((value) { + return Chip( + label: Text(value), + onDeleted: () { + setState(() { + selectedFilter[ + "filter_data"] + .remove(value); + }); + }, + ); + }).toList(), + ), + ), + ), + ], + ), + ); + }, + ); + }, + ), + ), + // Button Section + Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + onPressed: () { + // Clear all selected filters + selectedFilters.forEach((filter) { + filter["filter_data"].clear(); + }); + setState(() { + chartsData = List.from(originalChartsData); + cardData = List.from(originalCardData); + }); + // Reset the selectedFiltersStorage to empty when clearing + selectedFiltersStorage.clear(); + Navigator.pop(context); + }, + child: Text('Clear'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.grey, + ), + ), + ElevatedButton( + onPressed: () { + print( + "Selected Filters before applying: $selectedFilters"); + setState(() { + chartsData = originalChartsData; + cardData = originalCardData; + }); + + applyFilters(context, filters, chartsData, cardData, + selectedFilters); + // Save the selected filters to storage after applying + selectedFiltersStorage = List.from(selectedFilters); + }, + child: Text('Apply Filter'), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + key: _scaffoldKey, + backgroundColor: Colors.brown[200]!, + appBar: AppBar( + backgroundColor: Colors.brown[200]!, + elevation: 0, + leading: IconButton( + icon: Icon(Icons.arrow_back_ios_new, color: Colors.white), + onPressed: () { + context.go('/myhomepage'); + }, + ), + title: Text( + 'UAE Numbers', + style: TextStyle(color: Colors.white), + ), + ), + body: isLoading + ? Center(child: CircularProgressIndicator()) + : Column( + children: [ + Expanded( + child: ListView( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment + .end, // Aligns the row's content to the right + children: [ + Row( + children: [ + Text( + 'Bookmark', + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + Icon(Icons.bookmark_add_outlined, + color: Colors.white, size: 18), + ], + ), + SizedBox( + width: 10), // Horizontal space between items + Row( + children: [ + Text( + 'Share', + style: const TextStyle( + fontSize: 16, + color: Colors.white, + ), + ), + Icon(Icons.share_sharp, + color: Colors.white, size: 18), + ], + ), + SizedBox( + width: 10), // Horizontal space between items + IconButton( + icon: Icon(Icons.filter_alt_outlined, + color: Colors.white, size: 18), + onPressed: () { + showRightSideModal( + context, filterData, chartsData); + }, + ), + ], + ), + ], + ), + // nonChartData Cards + GridView.builder( + itemCount: cardData.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // 2 cards per row + crossAxisSpacing: 10, + mainAxisSpacing: 10, + childAspectRatio: 177.5 / + 180, // Adjusted to maintain width and fixed height + ), + itemBuilder: (context, index) { + final item = cardData[index]; + print('item item item $item'); + final chart_type = item['chart_type']; + final chart_heading = item['chart_heading']; + final data = apiService.processNonChartData(item); + print('processNonChartData'); + + if (chart_type == 'total') { + return Card( + margin: const EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.public, + color: Color(0xFF90B0D5), size: 22), + const SizedBox(height: 5), + Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + Text( + '(${data['lastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 10, color: Colors.grey), + ), + Text( + '${data['lastYearValue'] ?? 'NA'}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF90B0D5), + ), + ), + const Divider( + color: Colors.grey, thickness: 1), + Text( + '${data['secondLastYearValue'] ?? 'NA'}', + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Color(0xFF90B0D5), + ), + ), + Text( + '(${data['secondLastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 10, color: Colors.grey), + ), + ], + ), + ), + ); + } else if (chart_type == 'average') { + return Card( + margin: const EdgeInsets.all(10), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 4, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.analytics, + color: Color(0xFF90B0D5), size: 22), + const SizedBox(height: 5), + Text( + '${chart_heading ?? 'NA'}', + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + ), + Text( + '(${data['firstYear'] ?? 'NA'} - ${data['lastYear'] ?? 'NA'})', + style: const TextStyle( + fontSize: 10, color: Colors.grey), + ), + Text( + '${data['roundedAverage'] ?? 'NA'}', + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Color(0xFF90B0D5), + ), + ), + ], + ), + ), + ); + } else { + return const SizedBox + .shrink(); // Ignore unknown KPIs + } + }, + ), + // chartsData Cards + ListView.builder( + itemCount: chartsData.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return Card( + margin: const EdgeInsets.all(10), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 10), + Container( + height: 300, + // child: buildChart(chartsData[index]), + child: ChartWidget( + chartData: chartsData[index])), + ], + ), + ), + ); + }, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/Screens/charts/services/api_service.dart b/lib/presentation/Screens/charts/services/api_service.dart new file mode 100644 index 00000000..02952e20 --- /dev/null +++ b/lib/presentation/Screens/charts/services/api_service.dart @@ -0,0 +1,184 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; +import 'dart:collection'; + +class ChartData { + final DateTime? xDateTime; + final String? x; // Optional for non-DateTime-based charts + final double y; + + ChartData({this.xDateTime, this.x, required this.y}); +} + +class ApiService { + static const String baseUrl = 'https://pb.venbait.in/api/getDataSet'; + + Future> fetchChartData(String dataSets) async { + List isChartData = []; + List nonChartData = []; + List originalChartsData = []; + List originalCardData = []; + + final url = Uri.parse('$baseUrl?dataset=$dataSets'); + + try { + final response = await http.get(url); + print('data'); + if (response.statusCode == 200) { + final Map jsonData = jsonDecode(response.body); + // Variables to store filtered data + for (var item in jsonData['data']) { + if (item['is_chart'] == 'true') { + isChartData.add(item); + } else if (item['is_chart'] == 'false') { + nonChartData.add(item); + } + } + + // Save original data for reset + // originalChartsData = List.from(isChartData); // Store original data + // originalCardData = List.from(nonChartData); // Store original data + + // Return the data as a Map + return { + 'isChartData': isChartData, + 'nonChartData': nonChartData, + 'filterData': jsonData['filter_data'] + // 'originalChartsData': originalChartsData, + // 'originalCardData': originalCardData, + }; + } else { + throw Exception('Failed to load data'); + } + } catch (error) { + print('Error fetching data: $error'); + return {}; + } + } + + /// Fetch chart data and categorize it into chart and non-chart data + // Future fetchChartData(String dataSets) async { + // final url = Uri.parse('$baseUrl?dataset=$dataSets'); + // + // List isChartData = []; + // List nonChartData = []; + // List originalChartsData = []; + // List originalCardData = []; + // + // try { + // final response = await http.get(url); + // + // if (response.statusCode == 200) { + // final List data = jsonDecode(response.body); + // + // for (var item in data) { + // if (item['is_chart'] == 'true') { + // isChartData.add(item); + // } else if (item['is_chart'] == 'false') { + // nonChartData.add(item); + // } + // } + // + // print('Chart Data: $isChartData'); + // print('Non-Chart Data: $nonChartData'); + // + // originalChartsData = List.from(isChartData); + // originalCardData = List.from(nonChartData); + // + // print('chartsData $isChartData'); + // print('cardData $nonChartData'); + // } else { + // throw Exception('Failed to load data'); + // } + // } catch (error) { + // print('Error fetching data: $error'); + // } + // } + + /// Process non-chart data by extracting and calculating values + Map processNonChartData(dynamic item) { + print('Processing non-chart data...'); + + try { + final response = item['response']; + + final List responseData = response is LinkedHashMap + ? Map.from(response) + : response; + + final sortedResponse = List.from( + responseData.map((e) => e['ObsKey']['TIME_PERIOD'].toString()), + )..sort((a, b) => b.compareTo(a)); + + String? lastYear = + sortedResponse.isNotEmpty ? sortedResponse.first : null; + String? secondLastYear = + sortedResponse.length > 1 ? sortedResponse[1] : null; + String? firstYear = + sortedResponse.isNotEmpty ? sortedResponse.last : null; + + String? lastYearValue = response.firstWhere( + (item) => item['ObsKey']['TIME_PERIOD'].toString() == lastYear, + orElse: () => null, + )?['ObsValue']['Value']; + + String? secondLastYearValue = response.firstWhere( + (item) => item['ObsKey']['TIME_PERIOD'].toString() == secondLastYear, + orElse: () => null, + )?['ObsValue']['Value']; + + List allValues = responseData + .map((item) => + double.tryParse(item['ObsValue']['Value'].toString()) ?? 0.0) + .toList(); + + double sum = allValues.reduce((a, b) => a + b); + double average = sum / allValues.length; + double roundedAverage = average.roundToDouble(); + + return { + 'lastYear': lastYear ?? 'NA', + 'secondLastYear': secondLastYear ?? 'NA', + 'firstYear': firstYear ?? 'NA', + 'lastYearValue': lastYearValue ?? 'NA', + 'secondLastYearValue': secondLastYearValue ?? 'NA', + 'roundedAverage': roundedAverage, + 'allValues': allValues.isEmpty ? [0.0] : allValues, + }; + } catch (e) { + print('Error processing data: $e'); + return {}; + } + } + + /// Parse scatter chart data + List parseScatterChartData(List response) { + return response.map((entry) { + // Parse TIME_PERIOD into DateTime + final timePeriod = entry['ObsKey']?['TIME_PERIOD']; + final x = timePeriod != null + ? DateTime.tryParse('$timePeriod-01-01') // Convert year to DateTime + : DateTime.now(); // Fallback to current date if null + + // Parse Value into double + final y = + double.tryParse(entry['ObsValue']?['Value']?.toString() ?? '0.0') ?? + 0.0; + + return ChartData(xDateTime: x, y: y); + }).toList(); + } + + /// Parse stacked bar chart data + List parseStackedBarChartData( + dynamic chartData, String groupByKey, String groupByValue) { + return chartData['response'] + .where((entry) => entry['ObsKey'][groupByKey] == groupByValue) + .map((entry) { + return ChartData( + x: entry['ObsKey']['TIME_PERIOD'], + y: double.tryParse(entry['ObsValue']['Value']) ?? 0.0, + ); + }).toList(); + } +} diff --git a/lib/presentation/Screens/charts/widgets/chart_widget.dart b/lib/presentation/Screens/charts/widgets/chart_widget.dart new file mode 100644 index 00000000..4e7c4836 --- /dev/null +++ b/lib/presentation/Screens/charts/widgets/chart_widget.dart @@ -0,0 +1,632 @@ +import 'dart:math'; + +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; + +class ChartWidget extends StatelessWidget { + final dynamic chartData; + + ChartWidget({Key? key, required this.chartData}) : super(key: key); + + String capitalizeAndSplit(String input) { + return input + .split('_') + .map((word) => word[0].toUpperCase() + word.substring(1)) + .join(' '); + } + + List parseStackedBarChartData( + dynamic chartData, String groupByKey, String groupByValue) { + return chartData['response'] + .where((entry) => entry['ObsKey'][groupByKey] == groupByValue) + .map((entry) { + return ChartData( + x: entry['ObsKey']['TIME_PERIOD'], + y: double.tryParse(entry['ObsValue']['Value']) ?? 0.0, + ); + }).toList(); + } + + List parseColumnChartData(dynamic chartData) { + return chartData['response'].map((entry) { + return BarChartGroupData( + x: int.tryParse(entry['ObsKey']['TIME_PERIOD']) ?? 0, + barRods: [ + BarChartRodData( + toY: double.tryParse(entry['ObsValue']['Value']) ?? 0.0, + color: Colors.blue, + width: 16, + borderRadius: BorderRadius.zero, + ), + ], + ); + }).toList(); + } + + List parsePieChartData( + dynamic chartData, + double totalValue, + int? touchedIndex, + ) { + return chartData['response'] + .asMap() + .entries + .map((entry) { + int index = entry.key; + var data = entry.value; + double value = double.tryParse(data['ObsValue']['Value']) ?? 0.0; + double percentage = (value / totalValue) * 100; + bool isTouched = index == touchedIndex; + return PieChartSectionData( + value: value, + color: Colors.primaries[index % Colors.primaries.length], + title: '${percentage.toStringAsFixed(1)}%', + radius: isTouched ? 60 : 50, // Increase size when touched + titleStyle: TextStyle( + fontSize: isTouched ? 12 : 10, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ); + }).toList(); + } + + List generateIndicators(dynamic chartData, groupByValue) { + return chartData['response'].asMap().entries.map((entry) { + int index = entry.key; + var data = entry.value; + Color color = Colors.primaries[index % Colors.primaries.length]; + String title = data['ObsKey'][groupByValue] ?? ''; + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 8), + Text( + title, + style: TextStyle(fontSize: 14), + ), + ], + ); + }).toList(); + } + + Set extractGroupByValues(dynamic chartData, String groupByKey) { + return (chartData['response'] as List).map((entry) { + var value = entry['ObsKey'][groupByKey]; + return value != null + ? value.toString() + : ''; // Handle null and ensure conversion to String + }).toSet(); + } + + Widget buildChart(dynamic chartData) { + print('chartDataccccccc $chartData'); + // Extract group_by dynamically from the chartData + if (chartData == null || chartData['response'] == null) { + return Center(child: Text('No chart data available')); + } + + String groupByKey = chartData['group_by'] ?? ''; + print('groupByKey $groupByKey'); + Set groupByValues = extractGroupByValues(chartData, groupByKey); + print('groupByValues $groupByValues'); + + switch (chartData['chart_type']) { + case 'stacked_bar': + String chartTitle = + capitalizeAndSplit(chartData['chart_heading'] ?? ''); + // Define a list of colors + final List uniqueColors = [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + return SfCartesianChart( + primaryXAxis: CategoryAxis(), + title: ChartTitle(text: chartData['chart_heading']), + legend: Legend( + isVisible: true, + position: LegendPosition.bottom, + overflowMode: LegendItemOverflowMode.scroll, + ), + tooltipBehavior: TooltipBehavior( + enable: true, // Enable tooltips + format: 'point.x : point.y', // Custom tooltip format + ), + series: >[ + for (int i = 0; i < groupByValues.length; i++) + StackedBarSeries( + dataSource: parseStackedBarChartData( + chartData, groupByKey, groupByValues.elementAt(i)), + xValueMapper: (ChartData data, _) => data.x ?? '', + yValueMapper: (ChartData data, _) => data.y, + name: groupByValues.elementAt(i), + color: uniqueColors[i % uniqueColors.length], + enableTooltip: true, + ), + ], + ); + case 'stacked_column': + String chartTitle = + capitalizeAndSplit(chartData['chart_heading'] ?? ''); + // Define a list of colors + final List uniqueColors = [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + return SfCartesianChart( + primaryXAxis: CategoryAxis(), + title: ChartTitle(text: chartData['chart_heading']), + legend: Legend( + isVisible: true, + position: LegendPosition.bottom, + overflowMode: LegendItemOverflowMode.scroll, + ), + tooltipBehavior: TooltipBehavior( + enable: true, // Enable tooltips + format: 'point.x : point.y', // Custom tooltip format + ), + series: >[ + for (int i = 0; i < groupByValues.length; i++) + StackedColumnSeries( + dataSource: parseStackedBarChartData( + chartData, groupByKey, groupByValues.elementAt(i)), + xValueMapper: (ChartData data, _) => data.x ?? '', + yValueMapper: (ChartData data, _) => data.y, + name: groupByValues.elementAt(i), + color: uniqueColors[i % uniqueColors.length], + enableTooltip: true, + ), + ], + ); + case 'column_chart': // Case 3 for fl_chart column chart + return BarChart( + BarChartData( + gridData: FlGridData(show: false), + titlesData: FlTitlesData(show: true), + borderData: FlBorderData(show: false), + barGroups: parseColumnChartData(chartData), + ), + ); + case 'pie_chart': + double totalValue = chartData['response'] + .map( + (entry) => double.tryParse(entry['ObsValue']['Value']) ?? 0.0) + .fold(0.0, (prev, element) => prev + element); + + ValueNotifier touchedIndex = ValueNotifier(null); + + return Column( + children: [ + Text( + chartData['chart_heading'] ?? + 'Pie Chart', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 20), + Expanded( + child: ValueListenableBuilder( + valueListenable: touchedIndex, + builder: (context, value, child) { + return PieChart( + PieChartData( + sections: parsePieChartData( + chartData, totalValue, touchedIndex.value), + borderData: FlBorderData(show: false), + sectionsSpace: 2, + centerSpaceRadius: 50, + pieTouchData: PieTouchData( + touchCallback: (FlTouchEvent event, pieTouchResponse) { + if (pieTouchResponse?.touchedSection != null && + event is! FlTapUpEvent) { + touchedIndex.value = pieTouchResponse! + .touchedSection!.touchedSectionIndex; + } else { + touchedIndex.value = + null; // Reset when not touching + } + }, + ), + ), + ); + }, + ), + ), + SizedBox(height: 20), + Padding( + padding: + const EdgeInsets.all(8.0), // Add spacing around indicators + child: Wrap( + spacing: 12, + runSpacing: 8, + children: generateIndicators(chartData, chartData['group_by']), + ), + ), + ], + ); + case 'fl_stacked_bar': + // Divide groupByValuesList into chunks of 5 items per row + List groupByValuesList = groupByValues.toList(); + int chunkSize = 5; + List> chunkedGroupByValues = []; + for (int i = 0; i < groupByValuesList.length; i += chunkSize) { + chunkedGroupByValues.add( + groupByValuesList.sublist( + i, + i + chunkSize > groupByValuesList.length + ? groupByValuesList.length + : i + chunkSize), + ); + } + return Column( + children: [ + Text( + chartData['chart_heading'] ?? + 'Pie Chart', // Chart title from data + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w400, + ), + textAlign: TextAlign.center, + ), + SizedBox(height: 10), + AspectRatio( + aspectRatio: 1.5, + child: BarChart( + BarChartData( + alignment: BarChartAlignment.spaceBetween, + barTouchData: BarTouchData( + touchTooltipData: BarTouchTooltipData( + getTooltipItem: + (groupData, groupIndex, rodData, rodIndex) { + // print('groupData $groupData'); + // print('groupIndex $groupIndex'); + // print('rodData $rodData'); + // print('rodIndex $rodIndex'); + // Assume groupedData is passed as a parameter + // List timePeriods = + // groupedData.values.first.keys.toList(); + // String timePeriod = timePeriods[groupIndex]; + // List rodStackItems = + // rodData.rodStackItems; + // + // String tooltipText = ''; + // for (int i = 0; i < rodStackItems.length; i++) { + // double fromY = rodStackItems[i].fromY; + // double toY = rodStackItems[i].toY; + // double value = toY - fromY; + // String groupName = groupedData.keys.elementAt(i); + // + // tooltipText += + // '$timePeriod - ${value.toStringAsFixed(0)} - $groupName\n'; + // } + // + // return BarTooltipItem( + // tooltipText, + // TextStyle(color: Colors.white, fontSize: 12), + // ); + }, + ), + // touchCallback: (BarTouchResponse touchResponse) { + // // Handle touch events, such as hover + // if (touchResponse.touchInput is PointerDownEvent) { + // // If you want to handle touch down events, you can add your logic here. + // print('Bar touched: ${touchResponse.touchInput}'); + // } + // }, + handleBuiltInTouches: true, // Enable built-in touch events + ), + titlesData: FlTitlesData( + show: true, + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 28, + getTitlesWidget: (double value, TitleMeta meta) { + return _generateXTitles(chartData)[value.toInt()]; + }, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + getTitlesWidget: _generateLeftTitles, + ), + ), + topTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + rightTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + ), + gridData: FlGridData(show: true), + borderData: FlBorderData(show: false), + barGroups: _generateBarGroups(chartData, groupByKey), + ), + ), + ), + // Legend + Padding( + padding: const EdgeInsets.all(5.0), + child: Wrap( + spacing: 5, + runSpacing: 5, + children: chunkedGroupByValues.map((chunk) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: chunk.map((group) { + List groupByValuesList = groupByValues.toList(); + int index = groupByValuesList.indexOf(group); + Color groupColor = _getColorForGroup(index); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 5.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: groupColor, + shape: BoxShape.circle, + ), + ), + SizedBox(width: 3), + Text( + group, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 11), + ), + ], + ), + ); + }).toList(), + ); + }).toList(), + ), + ), + ], + ); + case 'fl_multi_bar': + return BarChart( + BarChartData( + alignment: BarChartAlignment.spaceAround, + maxY: 100, // Maximum value for the bar + barGroups: _buildBarGroups(), + titlesData: FlTitlesData( + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + return Text( + '${value.toInt()}%', + style: TextStyle(fontSize: 12), + ); + }, + ), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + return Text( + "Bar ${value.toInt()}", + style: TextStyle(fontSize: 12), + ); + }, + ), + ), + topTitles: AxisTitles(sideTitles: SideTitles(showTitles: false)), + rightTitles: + AxisTitles(sideTitles: SideTitles(showTitles: false)), + ), + borderData: FlBorderData( + show: false, + ), + barTouchData: BarTouchData(enabled: true), + gridData: FlGridData(show: false), + ), + ); + default: + return Center(child: Text('Unknown chart type')); + } + } + + // Define the list of unique colors + final List uniqueColors = [ + Color(0xFF648CBA), + Color(0xFF90B0D5), + Color(0xFF98BCE5), + Color(0xFFA7B5C5), + Color(0xFFBED3EC), + Color(0xFFD4E3F4), + ]; + + Color _getColorForGroup(int index) { + // Use the index to get a color from the uniqueColors list + return uniqueColors[index % uniqueColors.length]; + } + + List _generateBarGroups( + dynamic chartData, String groupByKey) { + Map> groupedData = {}; + + // Group data by `group_by` and `TIME_PERIOD`, accumulating the values for each group and year + for (var entry in chartData['response']) { + String groupValue = entry['ObsKey'][groupByKey]; + String timePeriod = entry['ObsKey']['TIME_PERIOD']; + double value = double.tryParse(entry['ObsValue']['Value']) ?? 0.0; + + // print('fl_groupValue $groupValue'); + // print('fl_timePeriod $timePeriod'); + // print('fl_value $value'); + + if (!groupedData.containsKey(groupValue)) { + groupedData[groupValue] = {}; + } + if (!groupedData[groupValue]!.containsKey(timePeriod)) { + groupedData[groupValue]![timePeriod] = 0.0; + } + groupedData[groupValue]![timePeriod] = + groupedData[groupValue]![timePeriod]! + value; + } + + // Create BarChartGroupData for each group + List barGroups = []; + List timePeriods = groupedData.values.first.keys.toList(); + print('timePeriods $timePeriods'); + + // For each time period, generate a BarChartGroupData + for (int i = 0; i < timePeriods.length; i++) { + // Intermediate variable to track the current toY value as we stack bars + double currentToY = 0; + + List rodStackItems = []; + + // For each group (e.g., region), create stacked bars + int groupIndex = 0; // Track group index for color assignment + for (var group in groupedData.keys) { + double value = groupedData[group]![timePeriods[i]] ?? 0.0; + + // Add stacked item with current `toY` value and the incremented one + rodStackItems.add( + BarChartRodStackItem( + currentToY, // The current bottom of the stack + currentToY + value, // The new top of the stack + _getColorForGroup(groupIndex), // Color based on the index + ), + ); + + // Update currentToY after adding the value for this stack + currentToY += value; + + groupIndex++; // Increment group index for the next group + } + + // Create BarChartGroupData with the final stacked value + BarChartGroupData barGroup = BarChartGroupData( + x: i, // Index for the x-axis based on the time period + barRods: [ + BarChartRodData( + toY: currentToY, // Use the accumulated `currentToY` value + rodStackItems: rodStackItems, // Add the stacked items + width: 20, + borderRadius: BorderRadius.zero, + color: Colors + .transparent, // This will act as a container for stacked items + ), + ], + ); + + barGroups.add(barGroup); + } + + return barGroups; + } + + // Function to dynamically generate titles for the x-axis (time periods) + List _generateXTitles(dynamic chartData) { + Set timePeriods = {}; + for (var entry in chartData['response']) { + timePeriods.add(entry['ObsKey']['TIME_PERIOD']); + } + return timePeriods.map((period) { + return Text( + period, + style: TextStyle(fontSize: 12), + ); + }).toList(); + } + +// Function to dynamically set left axis titles (values) + Widget _generateLeftTitles(double value, TitleMeta meta) { + if (value % 1000 == 0) { + return Text(value.toStringAsFixed(0), style: TextStyle(fontSize: 10)); + } + return Container(); + } + + /// Build the data for the bar groups + List _buildBarGroups() { + return [ + BarChartGroupData( + x: 1, // Identifier for the group + barRods: [ + BarChartRodData( + toY: 40, // Value for the first part of the stack + color: Colors.blue, + width: 10, + ), + BarChartRodData( + toY: 60, // Value for the second part of the stack + color: Colors.green, + width: 10, + ), + ], + ), + BarChartGroupData( + x: 2, + barRods: [ + BarChartRodData( + toY: 30, + color: Colors.orange, + width: 10, + ), + BarChartRodData( + toY: 70, + color: Colors.red, + width: 10, + ), + ], + ), + BarChartGroupData( + x: 3, + barRods: [ + BarChartRodData( + toY: 50, + color: Colors.purple, + width: 10, + ), + BarChartRodData( + toY: 50, + color: Colors.cyan, + width: 10, + ), + ], + ), + ]; + } + + @override + Widget build(BuildContext context) { + return buildChart(chartData); + } +} + +class ChartData { + final String? x; + final double y; + final DateTime? xDateTime; + + ChartData({this.x, required this.y, this.xDateTime}); +} diff --git a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart index 70ed8841..a2a7dcca 100644 --- a/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart +++ b/lib/presentation/routes/bottom_bar_routes/tab_routes/home_route.dart @@ -177,11 +177,11 @@ void handleInfoCardClick(BuildContext context, String data) { final dataSets = data; print(dataSets); if (dataSets == 'hotels') { - context.go('/Chart/$dataSets'); + context.go('/chartScreen/$dataSets'); } else if (dataSets == 'divorces') { - context.go('/Chart/$dataSets'); + context.go('/chartScreen/$dataSets'); } else if (dataSets == 'marriages') { - context.go('/Chart/$dataSets'); + context.go('/chartScreen/$dataSets'); } } diff --git a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart index cad24495..7138bcba 100644 --- a/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart +++ b/lib/presentation/routes/drawer_routes/custom_drawer_routes.dart @@ -270,7 +270,7 @@ class _BaseScaffoldState extends State { switch (route) { case '/myhomepage': return 0; - case '/about': + case '/uaenumbers': return 1; case '/settings': return 2; @@ -288,7 +288,7 @@ class _BaseScaffoldState extends State { context.go('/myhomepage'); break; case 1: - context.go('/about'); + context.go('/uaenumbers'); break; case 2: context.go('/settings'); diff --git a/pubspec.lock b/pubspec.lock index 3996cee4..8964aeb9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -511,6 +511,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + flutter_staggered_grid_view: + dependency: "direct main" + description: + name: flutter_staggered_grid_view + sha256: "19e7abb550c96fbfeb546b23f3ff356ee7c59a019a651f8f102a4ba9b7349395" + url: "https://pub.dev" + source: hosted + version: "0.7.0" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index a1142e8c..d0822126 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -56,6 +56,7 @@ dependencies: image_picker: ^1.1.2 syncfusion_flutter_charts: 28.1.33 material_charts: ^0.0.23 + flutter_staggered_grid_view: ^0.7.0 dependency_overrides: fading_edge_scrollview: ^4.1.1