1.0.0
This commit is contained in:
43
example/lib/cubits/app/app_cubit.dart
Normal file
43
example/lib/cubits/app/app_cubit.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'package:fl_chart_app/urls.dart';
|
||||
import 'package:fl_chart_app/util/app_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
part 'app_state.dart';
|
||||
|
||||
class AppCubit extends Cubit<AppState> {
|
||||
AppCubit() : super(const AppState()) {
|
||||
initialize();
|
||||
}
|
||||
|
||||
void initialize() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
emit(state.copyWith(
|
||||
currentPackageInfo: packageInfo,
|
||||
availableVersionToUpdate: '',
|
||||
usingFlChartVersion: BuildConstants.usingFlChartVersion,
|
||||
showDownloadNativeAppButton: kIsWeb || kIsWasm,
|
||||
));
|
||||
}
|
||||
|
||||
void onVersionClicked() {
|
||||
AppUtils().tryToLaunchUrl(
|
||||
Urls.getVersionReleaseUrl(state.usingFlChartVersion),
|
||||
);
|
||||
}
|
||||
|
||||
void hideDownloadNativeAppButton() {
|
||||
emit(state.copyWith(
|
||||
showDownloadNativeAppButton: false,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
class BuildConstants {
|
||||
static const String usingFlChartVersion = String.fromEnvironment(
|
||||
'USING_FL_CHART_VERSION',
|
||||
defaultValue: '',
|
||||
);
|
||||
}
|
||||
39
example/lib/cubits/app/app_state.dart
Normal file
39
example/lib/cubits/app/app_state.dart
Normal file
@@ -0,0 +1,39 @@
|
||||
part of 'app_cubit.dart';
|
||||
|
||||
class AppState extends Equatable {
|
||||
final PackageInfo? currentPackageInfo;
|
||||
final String availableVersionToUpdate;
|
||||
final String usingFlChartVersion;
|
||||
final bool showDownloadNativeAppButton;
|
||||
|
||||
String? get appVersion => currentPackageInfo?.version;
|
||||
|
||||
const AppState([
|
||||
this.currentPackageInfo,
|
||||
this.availableVersionToUpdate = '',
|
||||
this.usingFlChartVersion = '',
|
||||
this.showDownloadNativeAppButton = false,
|
||||
]);
|
||||
|
||||
AppState copyWith({
|
||||
PackageInfo? currentPackageInfo,
|
||||
String? availableVersionToUpdate,
|
||||
String? usingFlChartVersion,
|
||||
bool? showDownloadNativeAppButton,
|
||||
}) {
|
||||
return AppState(
|
||||
currentPackageInfo ?? this.currentPackageInfo,
|
||||
availableVersionToUpdate ?? this.availableVersionToUpdate,
|
||||
usingFlChartVersion ?? this.usingFlChartVersion,
|
||||
showDownloadNativeAppButton ?? this.showDownloadNativeAppButton,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
currentPackageInfo,
|
||||
availableVersionToUpdate,
|
||||
usingFlChartVersion,
|
||||
showDownloadNativeAppButton,
|
||||
];
|
||||
}
|
||||
38
example/lib/main.dart
Normal file
38
example/lib/main.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:fl_chart_app/cubits/app/app_cubit.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import 'presentation/router/app_router.dart';
|
||||
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<AppCubit>(create: (BuildContext context) => AppCubit()),
|
||||
],
|
||||
child: MaterialApp.router(
|
||||
title: AppTexts.appName,
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
useMaterial3: true,
|
||||
textTheme: GoogleFonts.assistantTextTheme(
|
||||
Theme.of(context).textTheme.apply(
|
||||
bodyColor: AppColors.mainTextColor3,
|
||||
),
|
||||
),
|
||||
scaffoldBackgroundColor: AppColors.pageBackground,
|
||||
),
|
||||
routerConfig: appRouterConfig,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
166
example/lib/presentation/menu/app_menu.dart
Normal file
166
example/lib/presentation/menu/app_menu.dart
Normal file
@@ -0,0 +1,166 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:fl_chart_app/cubits/app/app_cubit.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/urls.dart';
|
||||
import 'package:fl_chart_app/util/app_helper.dart';
|
||||
import 'package:fl_chart_app/util/app_utils.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import 'fl_chart_banner.dart';
|
||||
import 'menu_row.dart';
|
||||
|
||||
class AppMenu extends StatefulWidget {
|
||||
final List<ChartMenuItem> menuItems;
|
||||
final int currentSelectedIndex;
|
||||
final Function(int, ChartMenuItem) onItemSelected;
|
||||
final VoidCallback? onBannerClicked;
|
||||
|
||||
const AppMenu({
|
||||
super.key,
|
||||
required this.menuItems,
|
||||
required this.currentSelectedIndex,
|
||||
required this.onItemSelected,
|
||||
required this.onBannerClicked,
|
||||
});
|
||||
|
||||
@override
|
||||
AppMenuState createState() => AppMenuState();
|
||||
}
|
||||
|
||||
class AppMenuState extends State<AppMenu> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
color: AppColors.itemsBackground,
|
||||
child: Column(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 3,
|
||||
child: Center(
|
||||
child: InkWell(
|
||||
onTap: widget.onBannerClicked,
|
||||
child: const FlChartBanner(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, position) {
|
||||
final menuItem = widget.menuItems[position];
|
||||
return MenuRow(
|
||||
text: menuItem.text,
|
||||
svgPath: menuItem.iconPath,
|
||||
isSelected: widget.currentSelectedIndex == position,
|
||||
onTap: () {
|
||||
widget.onItemSelected(position, menuItem);
|
||||
},
|
||||
onDocumentsTap: () async {
|
||||
final url = Uri.parse(menuItem.chartType.documentationUrl);
|
||||
if (await canLaunchUrl(url)) {
|
||||
await launchUrl(url);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: widget.menuItems.length,
|
||||
),
|
||||
),
|
||||
const _AppVersionRow(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AppVersionRow extends StatelessWidget {
|
||||
const _AppVersionRow();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<AppCubit, AppState>(builder: (context, state) {
|
||||
if (state.appVersion.isNullOrBlank) {
|
||||
return Container();
|
||||
}
|
||||
return Container(
|
||||
margin: const EdgeInsets.all(12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(10.0),
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: '',
|
||||
style: DefaultTextStyle.of(context).style,
|
||||
children: <TextSpan>[
|
||||
const TextSpan(text: 'App version: '),
|
||||
TextSpan(
|
||||
text: 'v${state.appVersion!}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
if (state.usingFlChartVersion.isNotBlank) ...[
|
||||
TextSpan(
|
||||
text: '\nfl_chart: ',
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = BlocProvider.of<AppCubit>(context)
|
||||
.onVersionClicked,
|
||||
),
|
||||
TextSpan(
|
||||
text: 'v${state.usingFlChartVersion}',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = BlocProvider.of<AppCubit>(context)
|
||||
.onVersionClicked,
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
state.availableVersionToUpdate.isNotBlank
|
||||
? TextButton(
|
||||
onPressed: () {},
|
||||
child: Text(
|
||||
'Update to ${state.availableVersionToUpdate}',
|
||||
style: const TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
)
|
||||
: TextButton(
|
||||
onPressed: () => AppUtils().tryToLaunchUrl(Urls.aboutUrl),
|
||||
child: const Text(
|
||||
'About',
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class ChartMenuItem {
|
||||
final ChartType chartType;
|
||||
final String text;
|
||||
final String iconPath;
|
||||
|
||||
const ChartMenuItem(this.chartType, this.text, this.iconPath);
|
||||
}
|
||||
38
example/lib/presentation/menu/fl_chart_banner.dart
Normal file
38
example/lib/presentation/menu/fl_chart_banner.dart
Normal file
@@ -0,0 +1,38 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class FlChartBanner extends StatelessWidget {
|
||||
const FlChartBanner({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
final maxWidth = constraints.maxWidth;
|
||||
|
||||
final imageSize = maxWidth / 5.14;
|
||||
final space = maxWidth / 16.0;
|
||||
final textWidth = maxWidth / 2.8;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: imageSize,
|
||||
),
|
||||
Image.asset(
|
||||
AppAssets.flChartLogoIcon,
|
||||
width: imageSize,
|
||||
height: imageSize,
|
||||
),
|
||||
SizedBox(
|
||||
width: space,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
AppAssets.flChartLogoText,
|
||||
width: textWidth,
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
99
example/lib/presentation/menu/menu_row.dart
Normal file
99
example/lib/presentation/menu/menu_row.dart
Normal file
@@ -0,0 +1,99 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class MenuRow extends StatefulWidget {
|
||||
final String text;
|
||||
final String svgPath;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback onDocumentsTap;
|
||||
|
||||
const MenuRow({
|
||||
super.key,
|
||||
required this.text,
|
||||
required this.svgPath,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
required this.onDocumentsTap,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MenuRow> createState() => _MenuRowState();
|
||||
}
|
||||
|
||||
class _MenuRowState extends State<MenuRow> {
|
||||
bool get _showSelectedState => widget.isSelected;
|
||||
|
||||
bool isHovered = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
onHover: (bool hovered) {
|
||||
setState(() {
|
||||
isHovered = hovered;
|
||||
});
|
||||
},
|
||||
onTap: widget.onTap,
|
||||
child: SizedBox(
|
||||
height: AppDimens.menuRowHeight,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 36,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
widget.svgPath,
|
||||
width: AppDimens.menuIconSize,
|
||||
height: AppDimens.menuIconSize,
|
||||
colorFilter:
|
||||
const ColorFilter.mode(AppColors.primary, BlendMode.srcIn),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 18,
|
||||
),
|
||||
Text(
|
||||
widget.text,
|
||||
style: TextStyle(
|
||||
color: _showSelectedState ? AppColors.primary : Colors.white,
|
||||
fontSize: AppDimens.menuTextSize,
|
||||
),
|
||||
),
|
||||
Expanded(child: Container()),
|
||||
_DocumentationIcon(onTap: widget.onDocumentsTap),
|
||||
const SizedBox(
|
||||
width: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DocumentationIcon extends StatelessWidget {
|
||||
const _DocumentationIcon({
|
||||
required this.onTap,
|
||||
});
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: AppDimens.menuDocumentationIconSize,
|
||||
height: AppDimens.menuDocumentationIconSize,
|
||||
child: IconButton(
|
||||
onPressed: onTap,
|
||||
icon: const Icon(
|
||||
Icons.article,
|
||||
color: AppColors.contentColorWhite,
|
||||
),
|
||||
tooltip: 'Documentation',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
42
example/lib/presentation/pages/chart_samples_page.dart
Normal file
42
example/lib/presentation/pages/chart_samples_page.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/presentation/samples/chart_samples.dart';
|
||||
import 'package:fl_chart_app/presentation/widgets/chart_holder.dart';
|
||||
import 'package:fl_chart_app/util/app_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||
|
||||
class ChartSamplesPage extends StatelessWidget {
|
||||
final ChartType chartType;
|
||||
|
||||
final samples = ChartSamples.samples;
|
||||
|
||||
ChartSamplesPage({
|
||||
super.key,
|
||||
required this.chartType,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.transparent,
|
||||
body: MasonryGridView.builder(
|
||||
itemCount: samples[chartType]!.length,
|
||||
key: ValueKey(chartType),
|
||||
padding: const EdgeInsets.only(
|
||||
left: AppDimens.chartSamplesSpace,
|
||||
right: AppDimens.chartSamplesSpace,
|
||||
top: AppDimens.chartSamplesSpace,
|
||||
bottom: AppDimens.chartSamplesSpace + 68,
|
||||
),
|
||||
crossAxisSpacing: AppDimens.chartSamplesSpace,
|
||||
mainAxisSpacing: AppDimens.chartSamplesSpace,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return ChartHolder(chartSample: samples[chartType]![index]);
|
||||
},
|
||||
gridDelegate: const SliverSimpleGridDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 600,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
116
example/lib/presentation/pages/home_page.dart
Normal file
116
example/lib/presentation/pages/home_page.dart
Normal file
@@ -0,0 +1,116 @@
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:fl_chart_app/cubits/app/app_cubit.dart';
|
||||
import 'package:fl_chart_app/presentation/menu/app_menu.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/presentation/widgets/download_native_app_button.dart';
|
||||
import 'package:fl_chart_app/urls.dart';
|
||||
import 'package:fl_chart_app/util/app_helper.dart';
|
||||
import 'package:fl_chart_app/util/app_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import 'chart_samples_page.dart';
|
||||
|
||||
class HomePage extends StatelessWidget {
|
||||
HomePage({
|
||||
super.key,
|
||||
required this.showingChartType,
|
||||
}) {
|
||||
_initMenuItems();
|
||||
}
|
||||
|
||||
void _initMenuItems() {
|
||||
_menuItemsIndices = {};
|
||||
_menuItems = ChartType.values.mapIndexed(
|
||||
(int index, ChartType type) {
|
||||
_menuItemsIndices[type] = index;
|
||||
return ChartMenuItem(
|
||||
type,
|
||||
type.displayName,
|
||||
type.assetIcon,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
}
|
||||
|
||||
final ChartType showingChartType;
|
||||
late final Map<ChartType, int> _menuItemsIndices;
|
||||
late final List<ChartMenuItem> _menuItems;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedMenuIndex = _menuItemsIndices[showingChartType]!;
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final needsDrawer = constraints.maxWidth <=
|
||||
AppDimens.menuMaxNeededWidth + AppDimens.chartBoxMinWidth;
|
||||
final appMenuWidget = AppMenu(
|
||||
menuItems: _menuItems,
|
||||
currentSelectedIndex: selectedMenuIndex,
|
||||
onItemSelected: (newIndex, chartMenuItem) {
|
||||
context.go('/${chartMenuItem.chartType.name}');
|
||||
if (needsDrawer) {
|
||||
/// to close the drawer
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
onBannerClicked: () => AppUtils().tryToLaunchUrl(Urls.flChartUrl),
|
||||
);
|
||||
final samplesSectionWidget =
|
||||
ChartSamplesPage(chartType: showingChartType);
|
||||
final body = needsDrawer
|
||||
? samplesSectionWidget
|
||||
: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: AppDimens.menuMaxNeededWidth,
|
||||
child: appMenuWidget,
|
||||
),
|
||||
Expanded(
|
||||
child: samplesSectionWidget,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
return BlocBuilder<AppCubit, AppState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
body,
|
||||
if (state.showDownloadNativeAppButton)
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: DownloadNativeAppButton(
|
||||
onClose: () => context
|
||||
.read<AppCubit>()
|
||||
.hideDownloadNativeAppButton(),
|
||||
onDownload: () =>
|
||||
AppUtils().tryToLaunchUrl(Urls.downloadUrl),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
drawer: needsDrawer
|
||||
? Drawer(
|
||||
child: appMenuWidget,
|
||||
)
|
||||
: null,
|
||||
appBar: needsDrawer
|
||||
? AppBar(
|
||||
elevation: 0,
|
||||
backgroundColor: Colors.transparent,
|
||||
title: Text(showingChartType.displayName),
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
16
example/lib/presentation/presentation_utils.dart
Normal file
16
example/lib/presentation/presentation_utils.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
class AppUtils {
|
||||
static String getFormattedCurrency(
|
||||
BuildContext context,
|
||||
double value, {
|
||||
bool noDecimals = true,
|
||||
}) {
|
||||
final germanFormat = NumberFormat.currency(
|
||||
symbol: '€',
|
||||
decimalDigits: noDecimals && value % 1 == 0 ? 0 : 2,
|
||||
);
|
||||
return germanFormat.format(value);
|
||||
}
|
||||
}
|
||||
23
example/lib/presentation/resources/app_assets.dart
Normal file
23
example/lib/presentation/resources/app_assets.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:fl_chart_app/util/app_helper.dart';
|
||||
|
||||
class AppAssets {
|
||||
static String getChartIcon(ChartType type) {
|
||||
switch (type) {
|
||||
case ChartType.line:
|
||||
return 'assets/icons/ic_line_chart.svg';
|
||||
case ChartType.bar:
|
||||
return 'assets/icons/ic_bar_chart.svg';
|
||||
case ChartType.pie:
|
||||
return 'assets/icons/ic_pie_chart.svg';
|
||||
case ChartType.scatter:
|
||||
return 'assets/icons/ic_scatter_chart.svg';
|
||||
case ChartType.radar:
|
||||
return 'assets/icons/ic_radar_chart.svg';
|
||||
case ChartType.candlestick:
|
||||
return 'assets/icons/ic_candle_chart.svg';
|
||||
}
|
||||
}
|
||||
|
||||
static const flChartLogoIcon = 'assets/icons/fl_chart_logo_icon.png';
|
||||
static const flChartLogoText = 'assets/icons/fl_chart_logo_text.svg';
|
||||
}
|
||||
25
example/lib/presentation/resources/app_colors.dart
Normal file
25
example/lib/presentation/resources/app_colors.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppColors {
|
||||
static const Color primary = contentColorCyan;
|
||||
static const Color menuBackground = Color(0xFF090912);
|
||||
static const Color itemsBackground = Color(0xFF1B2339);
|
||||
static const Color pageBackground = Color(0xFF282E45);
|
||||
static const Color mainTextColor1 = Colors.white;
|
||||
static const Color mainTextColor2 = Colors.white70;
|
||||
static const Color mainTextColor3 = Colors.white38;
|
||||
static const Color mainGridLineColor = Colors.white10;
|
||||
static const Color borderColor = Colors.white54;
|
||||
static const Color gridLinesColor = Color(0x11FFFFFF);
|
||||
|
||||
static const Color contentColorBlack = Colors.black;
|
||||
static const Color contentColorWhite = Colors.white;
|
||||
static const Color contentColorBlue = Color(0xFF2196F3);
|
||||
static const Color contentColorYellow = Color(0xFFFFC300);
|
||||
static const Color contentColorOrange = Color(0xFFFF683B);
|
||||
static const Color contentColorGreen = Color(0xFF3BFF49);
|
||||
static const Color contentColorPurple = Color(0xFF6E1BFF);
|
||||
static const Color contentColorPink = Color(0xFFFF3AF2);
|
||||
static const Color contentColorRed = Color(0xFFE80054);
|
||||
static const Color contentColorCyan = Color(0xFF50E4FF);
|
||||
}
|
||||
13
example/lib/presentation/resources/app_dimens.dart
Normal file
13
example/lib/presentation/resources/app_dimens.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
class AppDimens {
|
||||
static const double menuMaxNeededWidth = 304;
|
||||
static const double menuRowHeight = 74;
|
||||
static const double menuIconSize = 32;
|
||||
static const double menuDocumentationIconSize = 44;
|
||||
static const double menuTextSize = 20;
|
||||
|
||||
static const double chartBoxMinWidth = 350;
|
||||
|
||||
static const double defaultRadius = 8;
|
||||
static const double chartSamplesSpace = 32.0;
|
||||
static const double chartSamplesMinWidth = 350;
|
||||
}
|
||||
4
example/lib/presentation/resources/app_resources.dart
Normal file
4
example/lib/presentation/resources/app_resources.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
export 'app_colors.dart';
|
||||
export 'app_assets.dart';
|
||||
export 'app_dimens.dart';
|
||||
export 'app_texts.dart';
|
||||
3
example/lib/presentation/resources/app_texts.dart
Normal file
3
example/lib/presentation/resources/app_texts.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
class AppTexts {
|
||||
static const appName = 'FL Chart App';
|
||||
}
|
||||
37
example/lib/presentation/router/app_router.dart
Normal file
37
example/lib/presentation/router/app_router.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
import 'package:fl_chart_app/presentation/pages/home_page.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_colors.dart';
|
||||
import 'package:fl_chart_app/util/app_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
final appRouterConfig = GoRouter(
|
||||
routes: [
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (context, state) => Container(color: AppColors.pageBackground),
|
||||
redirect: (context, state) {
|
||||
return '/${ChartType.values.first.name}';
|
||||
},
|
||||
),
|
||||
...ChartType.values.map(
|
||||
(ChartType chartType) => GoRoute(
|
||||
path: '/${chartType.name}',
|
||||
pageBuilder: (BuildContext context, GoRouterState state) =>
|
||||
MaterialPage<void>(
|
||||
/// We set a key for HomePage to prevent recreate it
|
||||
/// when user choose a new chart type to show
|
||||
key: const ValueKey('home_page'),
|
||||
child: HomePage(showingChartType: chartType),
|
||||
),
|
||||
),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/:any',
|
||||
builder: (context, state) => Container(color: AppColors.pageBackground),
|
||||
redirect: (context, state) {
|
||||
// Unsupported path, we redirect it to /, which redirects it to /line
|
||||
return '/';
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
397
example/lib/presentation/samples/bar/bar_chart_sample1.dart
Normal file
397
example/lib/presentation/samples/bar/bar_chart_sample1.dart
Normal file
@@ -0,0 +1,397 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/util/extensions/color_extensions.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BarChartSample1 extends StatefulWidget {
|
||||
BarChartSample1({super.key});
|
||||
|
||||
List<Color> get availableColors => const <Color>[
|
||||
AppColors.contentColorPurple,
|
||||
AppColors.contentColorYellow,
|
||||
AppColors.contentColorBlue,
|
||||
AppColors.contentColorOrange,
|
||||
AppColors.contentColorPink,
|
||||
AppColors.contentColorRed,
|
||||
];
|
||||
|
||||
final Color barBackgroundColor =
|
||||
AppColors.contentColorWhite.darken().withValues(alpha: 0.3);
|
||||
final Color barColor = AppColors.contentColorWhite;
|
||||
final Color touchedBarColor = AppColors.contentColorGreen;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => BarChartSample1State();
|
||||
}
|
||||
|
||||
class BarChartSample1State extends State<BarChartSample1> {
|
||||
final Duration animDuration = const Duration(milliseconds: 250);
|
||||
|
||||
int touchedIndex = -1;
|
||||
|
||||
bool isPlaying = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
const Text(
|
||||
'Mingguan',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorGreen,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
'Grafik konsumsi kalori',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorGreen.darken(),
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 38,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: BarChart(
|
||||
isPlaying ? randomData() : mainBarData(),
|
||||
duration: animDuration,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
isPlaying ? Icons.pause : Icons.play_arrow,
|
||||
color: AppColors.contentColorGreen,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isPlaying = !isPlaying;
|
||||
if (isPlaying) {
|
||||
refreshState();
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BarChartGroupData makeGroupData(
|
||||
int x,
|
||||
double y, {
|
||||
bool isTouched = false,
|
||||
Color? barColor,
|
||||
double width = 22,
|
||||
List<int> showTooltips = const [],
|
||||
}) {
|
||||
barColor ??= widget.barColor;
|
||||
return BarChartGroupData(
|
||||
x: x,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: isTouched ? y + 1 : y,
|
||||
color: isTouched ? widget.touchedBarColor : barColor,
|
||||
width: width,
|
||||
borderSide: isTouched
|
||||
? BorderSide(color: widget.touchedBarColor.darken(80))
|
||||
: const BorderSide(color: Colors.white, width: 0),
|
||||
backDrawRodData: BackgroundBarChartRodData(
|
||||
show: true,
|
||||
toY: 20,
|
||||
color: widget.barBackgroundColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
showingTooltipIndicators: showTooltips,
|
||||
);
|
||||
}
|
||||
|
||||
List<BarChartGroupData> showingGroups() => List.generate(7, (i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
return makeGroupData(0, 5, isTouched: i == touchedIndex);
|
||||
case 1:
|
||||
return makeGroupData(1, 6.5, isTouched: i == touchedIndex);
|
||||
case 2:
|
||||
return makeGroupData(2, 5, isTouched: i == touchedIndex);
|
||||
case 3:
|
||||
return makeGroupData(3, 7.5, isTouched: i == touchedIndex);
|
||||
case 4:
|
||||
return makeGroupData(4, 9, isTouched: i == touchedIndex);
|
||||
case 5:
|
||||
return makeGroupData(5, 11.5, isTouched: i == touchedIndex);
|
||||
case 6:
|
||||
return makeGroupData(6, 6.5, isTouched: i == touchedIndex);
|
||||
default:
|
||||
return throw Error();
|
||||
}
|
||||
});
|
||||
|
||||
BarChartData mainBarData() {
|
||||
return BarChartData(
|
||||
barTouchData: BarTouchData(
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipColor: (_) => Colors.blueGrey,
|
||||
tooltipHorizontalAlignment: FLHorizontalAlignment.right,
|
||||
tooltipMargin: -10,
|
||||
getTooltipItem: (group, groupIndex, rod, rodIndex) {
|
||||
String weekDay;
|
||||
switch (group.x) {
|
||||
case 0:
|
||||
weekDay = 'Monday';
|
||||
break;
|
||||
case 1:
|
||||
weekDay = 'Tuesday';
|
||||
break;
|
||||
case 2:
|
||||
weekDay = 'Wednesday';
|
||||
break;
|
||||
case 3:
|
||||
weekDay = 'Thursday';
|
||||
break;
|
||||
case 4:
|
||||
weekDay = 'Friday';
|
||||
break;
|
||||
case 5:
|
||||
weekDay = 'Saturday';
|
||||
break;
|
||||
case 6:
|
||||
weekDay = 'Sunday';
|
||||
break;
|
||||
default:
|
||||
throw Error();
|
||||
}
|
||||
return BarTooltipItem(
|
||||
'$weekDay\n',
|
||||
const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
children: <TextSpan>[
|
||||
TextSpan(
|
||||
text: (rod.toY - 1).toString(),
|
||||
style: const TextStyle(
|
||||
color: Colors.white, //widget.touchedBarColor,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
touchCallback: (FlTouchEvent event, barTouchResponse) {
|
||||
setState(() {
|
||||
if (!event.isInterestedForInteractions ||
|
||||
barTouchResponse == null ||
|
||||
barTouchResponse.spot == null) {
|
||||
touchedIndex = -1;
|
||||
return;
|
||||
}
|
||||
touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex;
|
||||
});
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: getTitles,
|
||||
reservedSize: 38,
|
||||
),
|
||||
),
|
||||
leftTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
barGroups: showingGroups(),
|
||||
gridData: const FlGridData(show: false),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
);
|
||||
Widget text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = const Text('M', style: style);
|
||||
break;
|
||||
case 1:
|
||||
text = const Text('T', style: style);
|
||||
break;
|
||||
case 2:
|
||||
text = const Text('W', style: style);
|
||||
break;
|
||||
case 3:
|
||||
text = const Text('T', style: style);
|
||||
break;
|
||||
case 4:
|
||||
text = const Text('F', style: style);
|
||||
break;
|
||||
case 5:
|
||||
text = const Text('S', style: style);
|
||||
break;
|
||||
case 6:
|
||||
text = const Text('S', style: style);
|
||||
break;
|
||||
default:
|
||||
text = const Text('', style: style);
|
||||
break;
|
||||
}
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 16,
|
||||
child: text,
|
||||
);
|
||||
}
|
||||
|
||||
BarChartData randomData() {
|
||||
return BarChartData(
|
||||
barTouchData: const BarTouchData(
|
||||
enabled: false,
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: getTitles,
|
||||
reservedSize: 38,
|
||||
),
|
||||
),
|
||||
leftTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
barGroups: List.generate(7, (i) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
return makeGroupData(
|
||||
0,
|
||||
Random().nextInt(15).toDouble() + 6,
|
||||
barColor: widget.availableColors[
|
||||
Random().nextInt(widget.availableColors.length)],
|
||||
);
|
||||
case 1:
|
||||
return makeGroupData(
|
||||
1,
|
||||
Random().nextInt(15).toDouble() + 6,
|
||||
barColor: widget.availableColors[
|
||||
Random().nextInt(widget.availableColors.length)],
|
||||
);
|
||||
case 2:
|
||||
return makeGroupData(
|
||||
2,
|
||||
Random().nextInt(15).toDouble() + 6,
|
||||
barColor: widget.availableColors[
|
||||
Random().nextInt(widget.availableColors.length)],
|
||||
);
|
||||
case 3:
|
||||
return makeGroupData(
|
||||
3,
|
||||
Random().nextInt(15).toDouble() + 6,
|
||||
barColor: widget.availableColors[
|
||||
Random().nextInt(widget.availableColors.length)],
|
||||
);
|
||||
case 4:
|
||||
return makeGroupData(
|
||||
4,
|
||||
Random().nextInt(15).toDouble() + 6,
|
||||
barColor: widget.availableColors[
|
||||
Random().nextInt(widget.availableColors.length)],
|
||||
);
|
||||
case 5:
|
||||
return makeGroupData(
|
||||
5,
|
||||
Random().nextInt(15).toDouble() + 6,
|
||||
barColor: widget.availableColors[
|
||||
Random().nextInt(widget.availableColors.length)],
|
||||
);
|
||||
case 6:
|
||||
return makeGroupData(
|
||||
6,
|
||||
Random().nextInt(15).toDouble() + 6,
|
||||
barColor: widget.availableColors[
|
||||
Random().nextInt(widget.availableColors.length)],
|
||||
);
|
||||
default:
|
||||
return throw Error();
|
||||
}
|
||||
}),
|
||||
gridData: const FlGridData(show: false),
|
||||
);
|
||||
}
|
||||
|
||||
Future<dynamic> refreshState() async {
|
||||
setState(() {});
|
||||
await Future<dynamic>.delayed(
|
||||
animDuration + const Duration(milliseconds: 50),
|
||||
);
|
||||
if (isPlaying) {
|
||||
await refreshState();
|
||||
}
|
||||
}
|
||||
}
|
||||
283
example/lib/presentation/samples/bar/bar_chart_sample2.dart
Normal file
283
example/lib/presentation/samples/bar/bar_chart_sample2.dart
Normal file
@@ -0,0 +1,283 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/util/extensions/color_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BarChartSample2 extends StatefulWidget {
|
||||
BarChartSample2({super.key});
|
||||
final Color leftBarColor = AppColors.contentColorYellow;
|
||||
final Color rightBarColor = AppColors.contentColorRed;
|
||||
final Color avgColor =
|
||||
AppColors.contentColorOrange.avg(AppColors.contentColorRed);
|
||||
@override
|
||||
State<StatefulWidget> createState() => BarChartSample2State();
|
||||
}
|
||||
|
||||
class BarChartSample2State extends State<BarChartSample2> {
|
||||
final double width = 7;
|
||||
|
||||
late List<BarChartGroupData> rawBarGroups;
|
||||
late List<BarChartGroupData> showingBarGroups;
|
||||
|
||||
int touchedGroupIndex = -1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final barGroup1 = makeGroupData(0, 5, 12);
|
||||
final barGroup2 = makeGroupData(1, 16, 12);
|
||||
final barGroup3 = makeGroupData(2, 18, 5);
|
||||
final barGroup4 = makeGroupData(3, 20, 16);
|
||||
final barGroup5 = makeGroupData(4, 17, 6);
|
||||
final barGroup6 = makeGroupData(5, 19, 1.5);
|
||||
final barGroup7 = makeGroupData(6, 10, 1.5);
|
||||
|
||||
final items = [
|
||||
barGroup1,
|
||||
barGroup2,
|
||||
barGroup3,
|
||||
barGroup4,
|
||||
barGroup5,
|
||||
barGroup6,
|
||||
barGroup7,
|
||||
];
|
||||
|
||||
rawBarGroups = items;
|
||||
|
||||
showingBarGroups = rawBarGroups;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
makeTransactionsIcon(),
|
||||
const SizedBox(
|
||||
width: 38,
|
||||
),
|
||||
const Text(
|
||||
'Transactions',
|
||||
style: TextStyle(color: Colors.white, fontSize: 22),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
const Text(
|
||||
'state',
|
||||
style: TextStyle(color: Color(0xff77839a), fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 38,
|
||||
),
|
||||
Expanded(
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
maxY: 20,
|
||||
barTouchData: BarTouchData(
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipColor: ((group) {
|
||||
return Colors.grey;
|
||||
}),
|
||||
getTooltipItem: (a, b, c, d) => null,
|
||||
),
|
||||
touchCallback: (FlTouchEvent event, response) {
|
||||
if (response == null || response.spot == null) {
|
||||
setState(() {
|
||||
touchedGroupIndex = -1;
|
||||
showingBarGroups = List.of(rawBarGroups);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
touchedGroupIndex = response.spot!.touchedBarGroupIndex;
|
||||
|
||||
setState(() {
|
||||
if (!event.isInterestedForInteractions) {
|
||||
touchedGroupIndex = -1;
|
||||
showingBarGroups = List.of(rawBarGroups);
|
||||
return;
|
||||
}
|
||||
showingBarGroups = List.of(rawBarGroups);
|
||||
if (touchedGroupIndex != -1) {
|
||||
var sum = 0.0;
|
||||
for (final rod
|
||||
in showingBarGroups[touchedGroupIndex].barRods) {
|
||||
sum += rod.toY;
|
||||
}
|
||||
final avg = sum /
|
||||
showingBarGroups[touchedGroupIndex]
|
||||
.barRods
|
||||
.length;
|
||||
|
||||
showingBarGroups[touchedGroupIndex] =
|
||||
showingBarGroups[touchedGroupIndex].copyWith(
|
||||
barRods: showingBarGroups[touchedGroupIndex]
|
||||
.barRods
|
||||
.map((rod) {
|
||||
return rod.copyWith(
|
||||
toY: avg, color: widget.avgColor);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: bottomTitles,
|
||||
reservedSize: 42,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 28,
|
||||
interval: 1,
|
||||
getTitlesWidget: leftTitles,
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
barGroups: showingBarGroups,
|
||||
gridData: const FlGridData(show: false),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget leftTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
color: Color(0xff7589a2),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
);
|
||||
String text;
|
||||
if (value == 0) {
|
||||
text = '1K';
|
||||
} else if (value == 10) {
|
||||
text = '5K';
|
||||
} else if (value == 19) {
|
||||
text = '10K';
|
||||
} else {
|
||||
return Container();
|
||||
}
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 0,
|
||||
child: Text(text, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
Widget bottomTitles(double value, TitleMeta meta) {
|
||||
final titles = <String>['Mn', 'Te', 'Wd', 'Tu', 'Fr', 'St', 'Su'];
|
||||
|
||||
final Widget text = Text(
|
||||
titles[value.toInt()],
|
||||
style: const TextStyle(
|
||||
color: Color(0xff7589a2),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
);
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 16, //margin top
|
||||
child: text,
|
||||
);
|
||||
}
|
||||
|
||||
BarChartGroupData makeGroupData(int x, double y1, double y2) {
|
||||
return BarChartGroupData(
|
||||
barsSpace: 4,
|
||||
x: x,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: y1,
|
||||
color: widget.leftBarColor,
|
||||
width: width,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: y2,
|
||||
color: widget.rightBarColor,
|
||||
width: width,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget makeTransactionsIcon() {
|
||||
const width = 4.5;
|
||||
const space = 3.5;
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: width,
|
||||
height: 10,
|
||||
color: Colors.white.withValues(alpha: 0.4),
|
||||
),
|
||||
const SizedBox(
|
||||
width: space,
|
||||
),
|
||||
Container(
|
||||
width: width,
|
||||
height: 28,
|
||||
color: Colors.white.withValues(alpha: 0.8),
|
||||
),
|
||||
const SizedBox(
|
||||
width: space,
|
||||
),
|
||||
Container(
|
||||
width: width,
|
||||
height: 42,
|
||||
color: Colors.white.withValues(alpha: 1),
|
||||
),
|
||||
const SizedBox(
|
||||
width: space,
|
||||
),
|
||||
Container(
|
||||
width: width,
|
||||
height: 28,
|
||||
color: Colors.white.withValues(alpha: 0.8),
|
||||
),
|
||||
const SizedBox(
|
||||
width: space,
|
||||
),
|
||||
Container(
|
||||
width: width,
|
||||
height: 10,
|
||||
color: Colors.white.withValues(alpha: 0.4),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
209
example/lib/presentation/samples/bar/bar_chart_sample3.dart
Normal file
209
example/lib/presentation/samples/bar/bar_chart_sample3.dart
Normal file
@@ -0,0 +1,209 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/util/extensions/color_extensions.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class _BarChart extends StatelessWidget {
|
||||
const _BarChart();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
barTouchData: barTouchData,
|
||||
titlesData: titlesData,
|
||||
borderData: borderData,
|
||||
barGroups: barGroups,
|
||||
gridData: const FlGridData(show: false),
|
||||
alignment: BarChartAlignment.spaceAround,
|
||||
maxY: 20,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BarTouchData get barTouchData => BarTouchData(
|
||||
enabled: false,
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipColor: (group) => Colors.transparent,
|
||||
tooltipPadding: EdgeInsets.zero,
|
||||
tooltipMargin: 8,
|
||||
getTooltipItem: (
|
||||
BarChartGroupData group,
|
||||
int groupIndex,
|
||||
BarChartRodData rod,
|
||||
int rodIndex,
|
||||
) {
|
||||
return BarTooltipItem(
|
||||
rod.toY.round().toString(),
|
||||
const TextStyle(
|
||||
color: AppColors.contentColorCyan,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Widget getTitles(double value, TitleMeta meta) {
|
||||
final style = TextStyle(
|
||||
color: AppColors.contentColorBlue.darken(20),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = 'Mn';
|
||||
break;
|
||||
case 1:
|
||||
text = 'Te';
|
||||
break;
|
||||
case 2:
|
||||
text = 'Wd';
|
||||
break;
|
||||
case 3:
|
||||
text = 'Tu';
|
||||
break;
|
||||
case 4:
|
||||
text = 'Fr';
|
||||
break;
|
||||
case 5:
|
||||
text = 'St';
|
||||
break;
|
||||
case 6:
|
||||
text = 'Sn';
|
||||
break;
|
||||
default:
|
||||
text = '';
|
||||
break;
|
||||
}
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 4,
|
||||
child: Text(text, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
FlTitlesData get titlesData => FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
getTitlesWidget: getTitles,
|
||||
),
|
||||
),
|
||||
leftTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
);
|
||||
|
||||
FlBorderData get borderData => FlBorderData(
|
||||
show: false,
|
||||
);
|
||||
|
||||
LinearGradient get _barsGradient => LinearGradient(
|
||||
colors: [
|
||||
AppColors.contentColorBlue.darken(20),
|
||||
AppColors.contentColorCyan,
|
||||
],
|
||||
begin: Alignment.bottomCenter,
|
||||
end: Alignment.topCenter,
|
||||
);
|
||||
|
||||
List<BarChartGroupData> get barGroups => [
|
||||
BarChartGroupData(
|
||||
x: 0,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 8,
|
||||
gradient: _barsGradient,
|
||||
)
|
||||
],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 1,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 10,
|
||||
gradient: _barsGradient,
|
||||
)
|
||||
],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 2,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 14,
|
||||
gradient: _barsGradient,
|
||||
)
|
||||
],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 3,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 15,
|
||||
gradient: _barsGradient,
|
||||
)
|
||||
],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 4,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 13,
|
||||
gradient: _barsGradient,
|
||||
)
|
||||
],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 5,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 10,
|
||||
gradient: _barsGradient,
|
||||
)
|
||||
],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 6,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 16,
|
||||
gradient: _barsGradient,
|
||||
)
|
||||
],
|
||||
showingTooltipIndicators: [0],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
class BarChartSample3 extends StatefulWidget {
|
||||
const BarChartSample3({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => BarChartSample3State();
|
||||
}
|
||||
|
||||
class BarChartSample3State extends State<BarChartSample3> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const AspectRatio(
|
||||
aspectRatio: 1.6,
|
||||
child: _BarChart(),
|
||||
);
|
||||
}
|
||||
}
|
||||
352
example/lib/presentation/samples/bar/bar_chart_sample4.dart
Normal file
352
example/lib/presentation/samples/bar/bar_chart_sample4.dart
Normal file
@@ -0,0 +1,352 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/util/extensions/color_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BarChartSample4 extends StatefulWidget {
|
||||
BarChartSample4({super.key});
|
||||
|
||||
final Color dark = AppColors.contentColorCyan.darken(60);
|
||||
final Color normal = AppColors.contentColorCyan.darken(30);
|
||||
final Color light = AppColors.contentColorCyan;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => BarChartSample4State();
|
||||
}
|
||||
|
||||
class BarChartSample4State extends State<BarChartSample4> {
|
||||
Widget bottomTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(fontSize: 10);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = 'Apr';
|
||||
break;
|
||||
case 1:
|
||||
text = 'May';
|
||||
break;
|
||||
case 2:
|
||||
text = 'Jun';
|
||||
break;
|
||||
case 3:
|
||||
text = 'Jul';
|
||||
break;
|
||||
case 4:
|
||||
text = 'Aug';
|
||||
break;
|
||||
default:
|
||||
text = '';
|
||||
break;
|
||||
}
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(text, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
Widget leftTitles(double value, TitleMeta meta) {
|
||||
if (value == meta.max) {
|
||||
return Container();
|
||||
}
|
||||
const style = TextStyle(
|
||||
fontSize: 10,
|
||||
);
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(
|
||||
meta.formattedValue,
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.66,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final barsSpace = 4.0 * constraints.maxWidth / 400;
|
||||
final barsWidth = 8.0 * constraints.maxWidth / 400;
|
||||
return BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.center,
|
||||
barTouchData: const BarTouchData(
|
||||
enabled: false,
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 28,
|
||||
getTitlesWidget: bottomTitles,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 40,
|
||||
getTitlesWidget: leftTitles,
|
||||
),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
checkToShowHorizontalLine: (value) => value % 10 == 0,
|
||||
getDrawingHorizontalLine: (value) => FlLine(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.1),
|
||||
strokeWidth: 1,
|
||||
),
|
||||
drawVerticalLine: false,
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
groupsSpace: barsSpace,
|
||||
barGroups: getData(barsWidth, barsSpace),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<BarChartGroupData> getData(double barsWidth, double barsSpace) {
|
||||
return [
|
||||
BarChartGroupData(
|
||||
x: 0,
|
||||
barsSpace: barsSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 17000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 2000000000, widget.dark),
|
||||
BarChartRodStackItem(2000000000, 12000000000, widget.normal),
|
||||
BarChartRodStackItem(12000000000, 17000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 24000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 13000000000, widget.dark),
|
||||
BarChartRodStackItem(13000000000, 14000000000, widget.normal),
|
||||
BarChartRodStackItem(14000000000, 24000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 23000000000.5,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 6000000000.5, widget.dark),
|
||||
BarChartRodStackItem(6000000000.5, 18000000000, widget.normal),
|
||||
BarChartRodStackItem(18000000000, 23000000000.5, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 29000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 9000000000, widget.dark),
|
||||
BarChartRodStackItem(9000000000, 15000000000, widget.normal),
|
||||
BarChartRodStackItem(15000000000, 29000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 32000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 2000000000.5, widget.dark),
|
||||
BarChartRodStackItem(2000000000.5, 17000000000.5, widget.normal),
|
||||
BarChartRodStackItem(17000000000.5, 32000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 1,
|
||||
barsSpace: barsSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 31000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 11000000000, widget.dark),
|
||||
BarChartRodStackItem(11000000000, 18000000000, widget.normal),
|
||||
BarChartRodStackItem(18000000000, 31000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 35000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 14000000000, widget.dark),
|
||||
BarChartRodStackItem(14000000000, 27000000000, widget.normal),
|
||||
BarChartRodStackItem(27000000000, 35000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 31000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 8000000000, widget.dark),
|
||||
BarChartRodStackItem(8000000000, 24000000000, widget.normal),
|
||||
BarChartRodStackItem(24000000000, 31000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 15000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 6000000000.5, widget.dark),
|
||||
BarChartRodStackItem(6000000000.5, 12000000000.5, widget.normal),
|
||||
BarChartRodStackItem(12000000000.5, 15000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 17000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 9000000000, widget.dark),
|
||||
BarChartRodStackItem(9000000000, 15000000000, widget.normal),
|
||||
BarChartRodStackItem(15000000000, 17000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 2,
|
||||
barsSpace: barsSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 34000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 6000000000, widget.dark),
|
||||
BarChartRodStackItem(6000000000, 23000000000, widget.normal),
|
||||
BarChartRodStackItem(23000000000, 34000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 32000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 7000000000, widget.dark),
|
||||
BarChartRodStackItem(7000000000, 24000000000, widget.normal),
|
||||
BarChartRodStackItem(24000000000, 32000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 14000000000.5,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 1000000000.5, widget.dark),
|
||||
BarChartRodStackItem(1000000000.5, 12000000000, widget.normal),
|
||||
BarChartRodStackItem(12000000000, 14000000000.5, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 20000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 4000000000, widget.dark),
|
||||
BarChartRodStackItem(4000000000, 15000000000, widget.normal),
|
||||
BarChartRodStackItem(15000000000, 20000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 24000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 4000000000, widget.dark),
|
||||
BarChartRodStackItem(4000000000, 15000000000, widget.normal),
|
||||
BarChartRodStackItem(15000000000, 24000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
BarChartGroupData(
|
||||
x: 3,
|
||||
barsSpace: barsSpace,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: 14000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 1000000000.5, widget.dark),
|
||||
BarChartRodStackItem(1000000000.5, 12000000000, widget.normal),
|
||||
BarChartRodStackItem(12000000000, 14000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 27000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 7000000000, widget.dark),
|
||||
BarChartRodStackItem(7000000000, 25000000000, widget.normal),
|
||||
BarChartRodStackItem(25000000000, 27000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 29000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 6000000000, widget.dark),
|
||||
BarChartRodStackItem(6000000000, 23000000000, widget.normal),
|
||||
BarChartRodStackItem(23000000000, 29000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 16000000000.5,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 9000000000, widget.dark),
|
||||
BarChartRodStackItem(9000000000, 15000000000, widget.normal),
|
||||
BarChartRodStackItem(15000000000, 16000000000.5, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: 15000000000,
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(0, 7000000000, widget.dark),
|
||||
BarChartRodStackItem(7000000000, 12000000000.5, widget.normal),
|
||||
BarChartRodStackItem(12000000000.5, 15000000000, widget.light),
|
||||
],
|
||||
borderRadius: BorderRadius.zero,
|
||||
width: barsWidth,
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
360
example/lib/presentation/samples/bar/bar_chart_sample5.dart
Normal file
360
example/lib/presentation/samples/bar/bar_chart_sample5.dart
Normal file
@@ -0,0 +1,360 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/util/app_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BarChartSample5 extends StatefulWidget {
|
||||
const BarChartSample5({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => BarChartSample5State();
|
||||
}
|
||||
|
||||
class BarChartSample5State extends State<BarChartSample5> {
|
||||
static const double barWidth = 22;
|
||||
static const shadowOpacity = 0.2;
|
||||
static const mainItems = <int, List<double>>{
|
||||
0: [2, 3, 2.5, 8],
|
||||
1: [-1.8, -2.7, -3, -6.5],
|
||||
2: [1.5, 2, 3.5, 6],
|
||||
3: [1.5, 1.5, 4, 6.5],
|
||||
4: [-2, -2, -5, -9],
|
||||
5: [-1.2, -1.5, -4.3, -10],
|
||||
6: [1.2, 4.8, 5, 5],
|
||||
};
|
||||
int touchedIndex = -1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Widget bottomTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(color: Colors.white, fontSize: 10);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = 'Mon';
|
||||
break;
|
||||
case 1:
|
||||
text = 'Tue';
|
||||
break;
|
||||
case 2:
|
||||
text = 'Wed';
|
||||
break;
|
||||
case 3:
|
||||
text = 'Thu';
|
||||
break;
|
||||
case 4:
|
||||
text = 'Fri';
|
||||
break;
|
||||
case 5:
|
||||
text = 'Sat';
|
||||
break;
|
||||
case 6:
|
||||
text = 'Sun';
|
||||
break;
|
||||
default:
|
||||
text = '';
|
||||
break;
|
||||
}
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(text, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
Widget topTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(color: Colors.white, fontSize: 10);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = 'Mon';
|
||||
break;
|
||||
case 1:
|
||||
text = 'Tue';
|
||||
break;
|
||||
case 2:
|
||||
text = 'Wed';
|
||||
break;
|
||||
case 3:
|
||||
text = 'Thu';
|
||||
break;
|
||||
case 4:
|
||||
text = 'Fri';
|
||||
break;
|
||||
case 5:
|
||||
text = 'Sat';
|
||||
break;
|
||||
case 6:
|
||||
text = 'Sun';
|
||||
break;
|
||||
default:
|
||||
return Container();
|
||||
}
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(text, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
Widget leftTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(color: Colors.white, fontSize: 10);
|
||||
String text;
|
||||
if (value == 0) {
|
||||
text = '0';
|
||||
} else {
|
||||
text = '${value.toInt()}0k';
|
||||
}
|
||||
return SideTitleWidget(
|
||||
angle: AppUtils().degreeToRadian(value < 0 ? -45 : 45),
|
||||
meta: meta,
|
||||
space: 4,
|
||||
child: Text(
|
||||
text,
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget rightTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(color: Colors.white, fontSize: 10);
|
||||
String text;
|
||||
if (value == 0) {
|
||||
text = '0';
|
||||
} else {
|
||||
text = '${value.toInt()}0k';
|
||||
}
|
||||
return SideTitleWidget(
|
||||
angle: AppUtils().degreeToRadian(90),
|
||||
meta: meta,
|
||||
space: 0,
|
||||
child: Text(
|
||||
text,
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BarChartGroupData generateGroup(
|
||||
int x,
|
||||
double value1,
|
||||
double value2,
|
||||
double value3,
|
||||
double value4,
|
||||
) {
|
||||
final isTop = value1 > 0;
|
||||
final sum = value1 + value2 + value3 + value4;
|
||||
final isTouched = touchedIndex == x;
|
||||
return BarChartGroupData(
|
||||
x: x,
|
||||
groupVertically: true,
|
||||
showingTooltipIndicators: isTouched ? [0] : [],
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: sum,
|
||||
width: barWidth,
|
||||
borderRadius: isTop
|
||||
? const BorderRadius.only(
|
||||
topLeft: Radius.circular(6),
|
||||
topRight: Radius.circular(6),
|
||||
)
|
||||
: const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(6),
|
||||
bottomRight: Radius.circular(6),
|
||||
),
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(
|
||||
0,
|
||||
value1,
|
||||
AppColors.contentColorGreen,
|
||||
BorderSide(
|
||||
color: Colors.white,
|
||||
width: isTouched ? 2 : 0,
|
||||
),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
value1,
|
||||
value1 + value2,
|
||||
AppColors.contentColorYellow,
|
||||
BorderSide(
|
||||
color: Colors.white,
|
||||
width: isTouched ? 2 : 0,
|
||||
),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
value1 + value2,
|
||||
value1 + value2 + value3,
|
||||
AppColors.contentColorPink,
|
||||
BorderSide(
|
||||
color: Colors.white,
|
||||
width: isTouched ? 2 : 0,
|
||||
),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
value1 + value2 + value3,
|
||||
value1 + value2 + value3 + value4,
|
||||
AppColors.contentColorBlue,
|
||||
BorderSide(
|
||||
color: Colors.white,
|
||||
width: isTouched ? 2 : 0,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: -sum,
|
||||
width: barWidth,
|
||||
color: Colors.transparent,
|
||||
borderRadius: isTop
|
||||
? const BorderRadius.only(
|
||||
bottomLeft: Radius.circular(6),
|
||||
bottomRight: Radius.circular(6),
|
||||
)
|
||||
: const BorderRadius.only(
|
||||
topLeft: Radius.circular(6),
|
||||
topRight: Radius.circular(6),
|
||||
),
|
||||
rodStackItems: [
|
||||
BarChartRodStackItem(
|
||||
0,
|
||||
-value1,
|
||||
AppColors.contentColorGreen.withValues(
|
||||
alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
|
||||
const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
-value1,
|
||||
-(value1 + value2),
|
||||
AppColors.contentColorYellow.withValues(
|
||||
alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
|
||||
const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
-(value1 + value2),
|
||||
-(value1 + value2 + value3),
|
||||
AppColors.contentColorPink.withValues(
|
||||
alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
|
||||
const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
BarChartRodStackItem(
|
||||
-(value1 + value2 + value3),
|
||||
-(value1 + value2 + value3 + value4),
|
||||
AppColors.contentColorBlue.withValues(
|
||||
alpha: isTouched ? shadowOpacity * 2 : shadowOpacity),
|
||||
const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool isShadowBar(int rodIndex) => rodIndex == 1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 0.8,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 16),
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.center,
|
||||
maxY: 20,
|
||||
minY: -20,
|
||||
groupsSpace: 12,
|
||||
barTouchData: BarTouchData(
|
||||
handleBuiltInTouches: false,
|
||||
touchCallback: (FlTouchEvent event, barTouchResponse) {
|
||||
if (!event.isInterestedForInteractions ||
|
||||
barTouchResponse == null ||
|
||||
barTouchResponse.spot == null) {
|
||||
setState(() {
|
||||
touchedIndex = -1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
final rodIndex = barTouchResponse.spot!.touchedRodDataIndex;
|
||||
if (isShadowBar(rodIndex)) {
|
||||
setState(() {
|
||||
touchedIndex = -1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
touchedIndex = barTouchResponse.spot!.touchedBarGroupIndex;
|
||||
});
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
topTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 32,
|
||||
getTitlesWidget: topTitles,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 32,
|
||||
getTitlesWidget: bottomTitles,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: leftTitles,
|
||||
interval: 5,
|
||||
reservedSize: 42,
|
||||
),
|
||||
),
|
||||
rightTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: rightTitles,
|
||||
interval: 5,
|
||||
reservedSize: 42,
|
||||
),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
checkToShowHorizontalLine: (value) => value % 5 == 0,
|
||||
getDrawingHorizontalLine: (value) {
|
||||
if (value == 0) {
|
||||
return FlLine(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.1),
|
||||
strokeWidth: 3,
|
||||
);
|
||||
}
|
||||
return FlLine(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.05),
|
||||
strokeWidth: 0.8,
|
||||
);
|
||||
},
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
barGroups: mainItems.entries
|
||||
.map(
|
||||
(e) => generateGroup(
|
||||
e.key,
|
||||
e.value[0],
|
||||
e.value[1],
|
||||
e.value[2],
|
||||
e.value[3],
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
184
example/lib/presentation/samples/bar/bar_chart_sample6.dart
Normal file
184
example/lib/presentation/samples/bar/bar_chart_sample6.dart
Normal file
@@ -0,0 +1,184 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/presentation/widgets/legend_widget.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BarChartSample6 extends StatelessWidget {
|
||||
const BarChartSample6({super.key});
|
||||
|
||||
final pilateColor = AppColors.contentColorPurple;
|
||||
final cyclingColor = AppColors.contentColorCyan;
|
||||
final quickWorkoutColor = AppColors.contentColorBlue;
|
||||
final betweenSpace = 0.2;
|
||||
|
||||
BarChartGroupData generateGroupData(
|
||||
int x,
|
||||
double pilates,
|
||||
double quickWorkout,
|
||||
double cycling,
|
||||
) {
|
||||
return BarChartGroupData(
|
||||
x: x,
|
||||
groupVertically: true,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
fromY: 0,
|
||||
toY: pilates,
|
||||
color: pilateColor,
|
||||
width: 5,
|
||||
),
|
||||
BarChartRodData(
|
||||
fromY: pilates + betweenSpace,
|
||||
toY: pilates + betweenSpace + quickWorkout,
|
||||
color: quickWorkoutColor,
|
||||
width: 5,
|
||||
),
|
||||
BarChartRodData(
|
||||
fromY: pilates + betweenSpace + quickWorkout + betweenSpace,
|
||||
toY: pilates + betweenSpace + quickWorkout + betweenSpace + cycling,
|
||||
color: cyclingColor,
|
||||
width: 5,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget bottomTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(fontSize: 10);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = 'JAN';
|
||||
break;
|
||||
case 1:
|
||||
text = 'FEB';
|
||||
break;
|
||||
case 2:
|
||||
text = 'MAR';
|
||||
break;
|
||||
case 3:
|
||||
text = 'APR';
|
||||
break;
|
||||
case 4:
|
||||
text = 'MAY';
|
||||
break;
|
||||
case 5:
|
||||
text = 'JUN';
|
||||
break;
|
||||
case 6:
|
||||
text = 'JUL';
|
||||
break;
|
||||
case 7:
|
||||
text = 'AUG';
|
||||
break;
|
||||
case 8:
|
||||
text = 'SEP';
|
||||
break;
|
||||
case 9:
|
||||
text = 'OCT';
|
||||
break;
|
||||
case 10:
|
||||
text = 'NOV';
|
||||
break;
|
||||
case 11:
|
||||
text = 'DEC';
|
||||
break;
|
||||
default:
|
||||
text = '';
|
||||
}
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(text, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const Text(
|
||||
'Activity',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorBlue,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
LegendsListWidget(
|
||||
legends: [
|
||||
Legend('Pilates', pilateColor),
|
||||
Legend('Quick workouts', quickWorkoutColor),
|
||||
Legend('Cycling', cyclingColor),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
AspectRatio(
|
||||
aspectRatio: 2,
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.spaceBetween,
|
||||
titlesData: FlTitlesData(
|
||||
leftTitles: const AxisTitles(),
|
||||
rightTitles: const AxisTitles(),
|
||||
topTitles: const AxisTitles(),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: bottomTitles,
|
||||
reservedSize: 20,
|
||||
),
|
||||
),
|
||||
),
|
||||
barTouchData: const BarTouchData(enabled: false),
|
||||
borderData: FlBorderData(show: false),
|
||||
gridData: const FlGridData(show: false),
|
||||
barGroups: [
|
||||
generateGroupData(0, 2, 3, 2),
|
||||
generateGroupData(1, 2, 5, 1.7),
|
||||
generateGroupData(2, 1.3, 3.1, 2.8),
|
||||
generateGroupData(3, 3.1, 4, 3.1),
|
||||
generateGroupData(4, 0.8, 3.3, 3.4),
|
||||
generateGroupData(5, 2, 5.6, 1.8),
|
||||
generateGroupData(6, 1.3, 3.2, 2),
|
||||
generateGroupData(7, 2.3, 3.2, 3),
|
||||
generateGroupData(8, 2, 4.8, 2.5),
|
||||
generateGroupData(9, 1.2, 3.2, 2.5),
|
||||
generateGroupData(10, 1, 4.8, 3),
|
||||
generateGroupData(11, 2, 4.4, 2.8),
|
||||
],
|
||||
maxY: 11 + (betweenSpace * 3),
|
||||
extraLinesData: ExtraLinesData(
|
||||
horizontalLines: [
|
||||
HorizontalLine(
|
||||
y: 3.3,
|
||||
color: pilateColor,
|
||||
strokeWidth: 1,
|
||||
dashArray: [20, 4],
|
||||
),
|
||||
HorizontalLine(
|
||||
y: 8,
|
||||
color: quickWorkoutColor,
|
||||
strokeWidth: 1,
|
||||
dashArray: [20, 4],
|
||||
),
|
||||
HorizontalLine(
|
||||
y: 11,
|
||||
color: cyclingColor,
|
||||
strokeWidth: 1,
|
||||
dashArray: [20, 4],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
254
example/lib/presentation/samples/bar/bar_chart_sample7.dart
Normal file
254
example/lib/presentation/samples/bar/bar_chart_sample7.dart
Normal file
@@ -0,0 +1,254 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BarChartSample7 extends StatefulWidget {
|
||||
BarChartSample7({super.key});
|
||||
|
||||
final shadowColor = const Color(0xFFCCCCCC);
|
||||
final dataList = [
|
||||
const _BarData(AppColors.contentColorYellow, 18, 18),
|
||||
const _BarData(AppColors.contentColorGreen, 17, 8),
|
||||
const _BarData(AppColors.contentColorOrange, 10, 15),
|
||||
const _BarData(AppColors.contentColorPink, 2.5, 5),
|
||||
const _BarData(AppColors.contentColorBlue, 2, 2.5),
|
||||
const _BarData(AppColors.contentColorRed, 2, 2),
|
||||
];
|
||||
|
||||
@override
|
||||
State<BarChartSample7> createState() => _BarChartSample7State();
|
||||
}
|
||||
|
||||
class _BarChartSample7State extends State<BarChartSample7> {
|
||||
BarChartGroupData generateBarGroup(
|
||||
int x,
|
||||
Color color,
|
||||
double value,
|
||||
double shadowValue,
|
||||
) {
|
||||
return BarChartGroupData(
|
||||
x: x,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: value,
|
||||
color: color,
|
||||
width: 6,
|
||||
),
|
||||
BarChartRodData(
|
||||
toY: shadowValue,
|
||||
color: widget.shadowColor,
|
||||
width: 6,
|
||||
),
|
||||
],
|
||||
showingTooltipIndicators: touchedGroupIndex == x ? [0] : [],
|
||||
);
|
||||
}
|
||||
|
||||
int touchedGroupIndex = -1;
|
||||
|
||||
int rotationTurns = 1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(child: Container()),
|
||||
const Text(
|
||||
'Horizontal Bar Chart',
|
||||
style: TextStyle(
|
||||
color: AppColors.mainTextColor1,
|
||||
fontSize: 20,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Tooltip(
|
||||
message: 'Rotate the chart 90 degrees (cw)',
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
rotationTurns += 1;
|
||||
});
|
||||
},
|
||||
icon: RotatedBox(
|
||||
quarterTurns: rotationTurns - 1,
|
||||
child: const Icon(
|
||||
Icons.rotate_90_degrees_cw,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.4,
|
||||
child: BarChart(
|
||||
BarChartData(
|
||||
alignment: BarChartAlignment.spaceBetween,
|
||||
rotationQuarterTurns: rotationTurns,
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: Border.symmetric(
|
||||
horizontal: BorderSide(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.2),
|
||||
),
|
||||
),
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
leftTitles: const AxisTitles(
|
||||
drawBelowEverything: true,
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 36,
|
||||
getTitlesWidget: (value, meta) {
|
||||
final index = value.toInt();
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: _IconWidget(
|
||||
color: widget.dataList[index].color,
|
||||
isSelected: touchedGroupIndex == index,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
rightTitles: const AxisTitles(),
|
||||
topTitles: const AxisTitles(),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
getDrawingHorizontalLine: (value) => FlLine(
|
||||
color: AppColors.borderColor.withValues(alpha: 0.2),
|
||||
strokeWidth: 1,
|
||||
),
|
||||
),
|
||||
barGroups: widget.dataList.asMap().entries.map((e) {
|
||||
final index = e.key;
|
||||
final data = e.value;
|
||||
return generateBarGroup(
|
||||
index,
|
||||
data.color,
|
||||
data.value,
|
||||
data.shadowValue,
|
||||
);
|
||||
}).toList(),
|
||||
maxY: 20,
|
||||
barTouchData: BarTouchData(
|
||||
enabled: true,
|
||||
handleBuiltInTouches: false,
|
||||
touchTooltipData: BarTouchTooltipData(
|
||||
getTooltipColor: (group) => Colors.transparent,
|
||||
tooltipMargin: 0,
|
||||
getTooltipItem: (
|
||||
BarChartGroupData group,
|
||||
int groupIndex,
|
||||
BarChartRodData rod,
|
||||
int rodIndex,
|
||||
) {
|
||||
return BarTooltipItem(
|
||||
rod.toY.toString(),
|
||||
TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: rod.color,
|
||||
fontSize: 18,
|
||||
shadows: const [
|
||||
Shadow(
|
||||
color: Colors.black26,
|
||||
blurRadius: 12,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
touchCallback: (event, response) {
|
||||
if (event.isInterestedForInteractions &&
|
||||
response != null &&
|
||||
response.spot != null) {
|
||||
setState(() {
|
||||
touchedGroupIndex = response.spot!.touchedBarGroupIndex;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
touchedGroupIndex = -1;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BarData {
|
||||
const _BarData(this.color, this.value, this.shadowValue);
|
||||
|
||||
final Color color;
|
||||
final double value;
|
||||
final double shadowValue;
|
||||
}
|
||||
|
||||
class _IconWidget extends ImplicitlyAnimatedWidget {
|
||||
const _IconWidget({
|
||||
required this.color,
|
||||
required this.isSelected,
|
||||
}) : super(duration: const Duration(milliseconds: 300));
|
||||
final Color color;
|
||||
final bool isSelected;
|
||||
|
||||
@override
|
||||
ImplicitlyAnimatedWidgetState<ImplicitlyAnimatedWidget> createState() =>
|
||||
_IconWidgetState();
|
||||
}
|
||||
|
||||
class _IconWidgetState extends AnimatedWidgetBaseState<_IconWidget> {
|
||||
Tween<double>? _rotationTween;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final rotation = math.pi * 4 * _rotationTween!.evaluate(animation);
|
||||
final scale = 1 + _rotationTween!.evaluate(animation) * 0.5;
|
||||
return Transform(
|
||||
transform: Matrix4.rotationZ(rotation).scaled(scale, scale),
|
||||
origin: const Offset(14, 14),
|
||||
child: Icon(
|
||||
widget.isSelected ? Icons.face_retouching_natural : Icons.face,
|
||||
color: widget.color,
|
||||
size: 28,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void forEachTween(TweenVisitor<dynamic> visitor) {
|
||||
_rotationTween = visitor(
|
||||
_rotationTween,
|
||||
widget.isSelected ? 1.0 : 0.0,
|
||||
(dynamic value) => Tween<double>(
|
||||
begin: value as double,
|
||||
end: widget.isSelected ? 1.0 : 0.0,
|
||||
),
|
||||
) as Tween<double>?;
|
||||
}
|
||||
}
|
||||
174
example/lib/presentation/samples/bar/bar_chart_sample8.dart
Normal file
174
example/lib/presentation/samples/bar/bar_chart_sample8.dart
Normal file
@@ -0,0 +1,174 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/util/extensions/color_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BarChartSample8 extends StatefulWidget {
|
||||
BarChartSample8({super.key});
|
||||
|
||||
final Color barBackgroundColor =
|
||||
AppColors.contentColorWhite.darken().withValues(alpha: 0.3);
|
||||
final Color barColor = AppColors.contentColorWhite;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => BarChartSample1State();
|
||||
}
|
||||
|
||||
class BarChartSample1State extends State<BarChartSample8> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.graphic_eq),
|
||||
const SizedBox(
|
||||
width: 32,
|
||||
),
|
||||
Text(
|
||||
'Sales forecasting chart',
|
||||
style: TextStyle(
|
||||
color: widget.barColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 32,
|
||||
),
|
||||
Expanded(
|
||||
child: BarChart(
|
||||
randomData(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
BarChartGroupData makeGroupData(
|
||||
int x,
|
||||
double y,
|
||||
FlErrorRange errorRange,
|
||||
) {
|
||||
return BarChartGroupData(
|
||||
x: x,
|
||||
barRods: [
|
||||
BarChartRodData(
|
||||
toY: y,
|
||||
toYErrorRange: errorRange,
|
||||
color: x >= 4 ? Colors.transparent : widget.barColor,
|
||||
borderRadius: BorderRadius.zero,
|
||||
borderDashArray: x >= 4 ? [4, 4] : null,
|
||||
width: 22,
|
||||
borderSide: BorderSide(color: widget.barColor, width: 2.0),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget getTitles(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
);
|
||||
List<String> days = ['M', 'T', 'W', 'T', 'F', 'S', 'S'];
|
||||
|
||||
Widget text = Text(
|
||||
days[value.toInt()],
|
||||
style: style,
|
||||
);
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 16,
|
||||
child: text,
|
||||
);
|
||||
}
|
||||
|
||||
BarChartData randomData() {
|
||||
return BarChartData(
|
||||
maxY: 300.0,
|
||||
barTouchData: const BarTouchData(
|
||||
enabled: false,
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: getTitles,
|
||||
reservedSize: 38,
|
||||
),
|
||||
),
|
||||
leftTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
reservedSize: 30,
|
||||
showTitles: true,
|
||||
),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
barGroups: List.generate(
|
||||
7,
|
||||
(i) {
|
||||
final y = Random().nextInt(290).toDouble() + 10;
|
||||
final lowerBy = y < 50
|
||||
? Random().nextDouble() * 10
|
||||
: Random().nextDouble() * 30 + 5;
|
||||
final upperBy = y > 290
|
||||
? Random().nextDouble() * 10
|
||||
: Random().nextDouble() * 30 + 5;
|
||||
return makeGroupData(
|
||||
i,
|
||||
y,
|
||||
FlErrorRange(
|
||||
lowerBy: lowerBy,
|
||||
upperBy: upperBy,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
gridData: const FlGridData(show: false),
|
||||
errorIndicatorData: FlErrorIndicatorData(
|
||||
painter: _errorPainter,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
FlSpotErrorRangePainter _errorPainter(
|
||||
BarChartSpotErrorRangeCallbackInput input,
|
||||
) =>
|
||||
FlSimpleErrorPainter(
|
||||
lineWidth: 2.0,
|
||||
capLength: 14,
|
||||
lineColor: input.groupIndex < 4
|
||||
? AppColors.contentColorOrange
|
||||
: AppColors.primary.withValues(alpha: 0.5),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_colors.dart';
|
||||
import 'package:fl_chart_app/util/csv_parser.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class CandlestickChartSample1 extends StatefulWidget {
|
||||
const CandlestickChartSample1({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => CandlestickChartSample1State();
|
||||
}
|
||||
|
||||
class CandlestickChartSample1State extends State<CandlestickChartSample1> {
|
||||
List<List<_BtcCandlestickData>>? _btcMonthlyData;
|
||||
|
||||
int _currentMonthIndex = 0;
|
||||
late final List<String> monthsNames;
|
||||
|
||||
final int minDays = 1;
|
||||
final int maxDays = 31;
|
||||
late final FlLine _gridLine;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
monthsNames = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
_loadData();
|
||||
_gridLine = FlLine(
|
||||
color: Colors.blueGrey.withValues(alpha: 0.4),
|
||||
strokeWidth: 0.4,
|
||||
dashArray: [8, 4],
|
||||
);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _loadData() async {
|
||||
final data = await rootBundle
|
||||
.loadString('assets/data/bitcoin_2023-01-01_2023-12-31.csv');
|
||||
final rows = CsvParser.parse(data);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
final allData = rows.skip(1).map((row) {
|
||||
// 2023-12-31,2024-01-01
|
||||
return _BtcCandlestickData(
|
||||
datetime: DateTime.parse(row[0]),
|
||||
open: double.parse(row[2]),
|
||||
high: double.parse(row[3]),
|
||||
low: double.parse(row[4]),
|
||||
close: double.parse(row[5]),
|
||||
volume: double.parse(row[6]),
|
||||
marketCap: double.parse(row[7]),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
_btcMonthlyData = List.generate(12, (index) {
|
||||
final month = index + 1;
|
||||
final monthData = allData
|
||||
.where((element) => element.datetime.month == month)
|
||||
.toList();
|
||||
monthData.sort((a, b) => a.datetime.compareTo(b.datetime));
|
||||
return monthData;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 18),
|
||||
const Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'BTC Price 2024',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorYellow,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
onPressed: _canGoPrevious ? _previousMonth : null,
|
||||
icon: const Icon(Icons.navigate_before_rounded),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 92,
|
||||
child: Text(
|
||||
monthsNames[_currentMonthIndex],
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: AppColors.contentColorWhite,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: IconButton(
|
||||
onPressed: _canGoNext ? _nextMonth : null,
|
||||
icon: const Icon(Icons.navigate_next_rounded),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.5,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (_btcMonthlyData != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 0.0,
|
||||
right: 18.0,
|
||||
),
|
||||
child: CandlestickChart(
|
||||
CandlestickChartData(
|
||||
candlestickSpots: _btcMonthlyData![_currentMonthIndex]
|
||||
.asMap()
|
||||
.entries
|
||||
.map((entry) {
|
||||
final index = entry.key;
|
||||
final data = entry.value;
|
||||
return CandlestickSpot(
|
||||
x: index.toDouble(),
|
||||
open: data.open,
|
||||
high: data.high,
|
||||
low: data.low,
|
||||
close: data.close,
|
||||
);
|
||||
}).toList(),
|
||||
minX: 0,
|
||||
maxX: 31,
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
getDrawingHorizontalLine: (_) => _gridLine,
|
||||
getDrawingVerticalLine: (_) => _gridLine,
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
drawBelowEverything: true,
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
maxIncluded: false,
|
||||
minIncluded: false,
|
||||
reservedSize: 60,
|
||||
getTitlesWidget: _leftTitles,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
axisNameWidget: Container(
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
child: const Text(
|
||||
'Day of month',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorGreen,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
axisNameSize: 40,
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 38,
|
||||
maxIncluded: false,
|
||||
interval: 1,
|
||||
getTitlesWidget: _bottomTitles,
|
||||
),
|
||||
),
|
||||
),
|
||||
touchedPointIndicator: AxisSpotIndicator(
|
||||
painter: AxisLinesIndicatorPainter(
|
||||
verticalLineProvider: (x) {
|
||||
final data =
|
||||
_btcMonthlyData![_currentMonthIndex][x.toInt()];
|
||||
|
||||
return VerticalLine(
|
||||
x: x,
|
||||
color: (data.isUp
|
||||
? AppColors.contentColorGreen
|
||||
: AppColors.contentColorRed)
|
||||
.withValues(alpha: 0.5),
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
horizontalLineProvider: (y) => HorizontalLine(
|
||||
y: y,
|
||||
label: HorizontalLineLabel(
|
||||
show: true,
|
||||
style: const TextStyle(
|
||||
color: AppColors.contentColorYellow,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
labelResolver: (hLine) =>
|
||||
hLine.y.toInt().toString(),
|
||||
alignment: Alignment.topLeft),
|
||||
color: AppColors.contentColorYellow.withValues(
|
||||
alpha: 0.8,
|
||||
),
|
||||
strokeWidth: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_btcMonthlyData == null)
|
||||
const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool get _canGoNext => _currentMonthIndex < 11;
|
||||
|
||||
bool get _canGoPrevious => _currentMonthIndex > 0;
|
||||
|
||||
void _previousMonth() {
|
||||
if (!_canGoPrevious) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_currentMonthIndex--;
|
||||
});
|
||||
}
|
||||
|
||||
void _nextMonth() {
|
||||
if (!_canGoNext) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_currentMonthIndex++;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _bottomTitles(double value, TitleMeta meta) {
|
||||
final day = value.toInt() + 1;
|
||||
|
||||
final isImportantToShow = day % 5 == 0 || day == 1;
|
||||
|
||||
if (!isImportantToShow) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(
|
||||
day.toString(),
|
||||
style: const TextStyle(
|
||||
color: AppColors.contentColorGreen,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _leftTitles(double value, TitleMeta meta) {
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(
|
||||
meta.formattedValue,
|
||||
style: const TextStyle(
|
||||
color: AppColors.contentColorYellow,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _BtcCandlestickData with EquatableMixin {
|
||||
_BtcCandlestickData({
|
||||
required this.datetime,
|
||||
required this.open,
|
||||
required this.high,
|
||||
required this.low,
|
||||
required this.close,
|
||||
required this.volume,
|
||||
required this.marketCap,
|
||||
});
|
||||
|
||||
final DateTime datetime;
|
||||
final double open;
|
||||
final double high;
|
||||
final double low;
|
||||
final double close;
|
||||
final double volume;
|
||||
final double marketCap;
|
||||
|
||||
bool get isUp => open < close;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
datetime,
|
||||
open,
|
||||
high,
|
||||
low,
|
||||
close,
|
||||
volume,
|
||||
marketCap,
|
||||
];
|
||||
}
|
||||
48
example/lib/presentation/samples/chart_sample.dart
Normal file
48
example/lib/presentation/samples/chart_sample.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:fl_chart_app/urls.dart';
|
||||
import 'package:fl_chart_app/util/app_helper.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
|
||||
abstract class ChartSample {
|
||||
final int number;
|
||||
final WidgetBuilder builder;
|
||||
ChartType get type;
|
||||
String get name => '${type.displayName} Sample $number';
|
||||
String get url => Urls.getChartSourceCodeUrl(type, number);
|
||||
ChartSample(this.number, this.builder);
|
||||
}
|
||||
|
||||
class LineChartSample extends ChartSample {
|
||||
LineChartSample(super.number, super.builder);
|
||||
@override
|
||||
ChartType get type => ChartType.line;
|
||||
}
|
||||
|
||||
class BarChartSample extends ChartSample {
|
||||
BarChartSample(super.number, super.builder);
|
||||
@override
|
||||
ChartType get type => ChartType.bar;
|
||||
}
|
||||
|
||||
class PieChartSample extends ChartSample {
|
||||
PieChartSample(super.number, super.builder);
|
||||
@override
|
||||
ChartType get type => ChartType.pie;
|
||||
}
|
||||
|
||||
class ScatterChartSample extends ChartSample {
|
||||
ScatterChartSample(super.number, super.builder);
|
||||
@override
|
||||
ChartType get type => ChartType.scatter;
|
||||
}
|
||||
|
||||
class RadarChartSample extends ChartSample {
|
||||
RadarChartSample(super.number, super.builder);
|
||||
@override
|
||||
ChartType get type => ChartType.radar;
|
||||
}
|
||||
|
||||
class CandlestickChartSample extends ChartSample {
|
||||
CandlestickChartSample(super.number, super.builder);
|
||||
@override
|
||||
ChartType get type => ChartType.candlestick;
|
||||
}
|
||||
76
example/lib/presentation/samples/chart_samples.dart
Normal file
76
example/lib/presentation/samples/chart_samples.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:fl_chart_app/presentation/samples/candlestick/candlestick_chart_sample1.dart';
|
||||
import 'package:fl_chart_app/util/app_helper.dart';
|
||||
|
||||
import 'bar/bar_chart_sample1.dart';
|
||||
import 'bar/bar_chart_sample2.dart';
|
||||
import 'bar/bar_chart_sample3.dart';
|
||||
import 'bar/bar_chart_sample4.dart';
|
||||
import 'bar/bar_chart_sample5.dart';
|
||||
import 'bar/bar_chart_sample6.dart';
|
||||
import 'bar/bar_chart_sample7.dart';
|
||||
import 'bar/bar_chart_sample8.dart';
|
||||
import 'chart_sample.dart';
|
||||
import 'line/line_chart_sample1.dart';
|
||||
import 'line/line_chart_sample10.dart';
|
||||
import 'line/line_chart_sample11.dart';
|
||||
import 'line/line_chart_sample12.dart';
|
||||
import 'line/line_chart_sample13.dart';
|
||||
import 'line/line_chart_sample2.dart';
|
||||
import 'line/line_chart_sample3.dart';
|
||||
import 'line/line_chart_sample4.dart';
|
||||
import 'line/line_chart_sample5.dart';
|
||||
import 'line/line_chart_sample6.dart';
|
||||
import 'line/line_chart_sample7.dart';
|
||||
import 'line/line_chart_sample8.dart';
|
||||
import 'line/line_chart_sample9.dart';
|
||||
import 'pie/pie_chart_sample1.dart';
|
||||
import 'pie/pie_chart_sample2.dart';
|
||||
import 'pie/pie_chart_sample3.dart';
|
||||
import 'radar/radar_chart_sample1.dart';
|
||||
import 'scatter/scatter_chart_sample1.dart';
|
||||
import 'scatter/scatter_chart_sample2.dart';
|
||||
|
||||
class ChartSamples {
|
||||
static final Map<ChartType, List<ChartSample>> samples = {
|
||||
ChartType.line: [
|
||||
LineChartSample(1, (context) => const LineChartSample1()),
|
||||
LineChartSample(2, (context) => const LineChartSample2()),
|
||||
LineChartSample(3, (context) => LineChartSample3()),
|
||||
LineChartSample(4, (context) => LineChartSample4()),
|
||||
LineChartSample(5, (context) => const LineChartSample5()),
|
||||
LineChartSample(6, (context) => LineChartSample6()),
|
||||
LineChartSample(7, (context) => LineChartSample7()),
|
||||
LineChartSample(8, (context) => const LineChartSample8()),
|
||||
LineChartSample(9, (context) => LineChartSample9()),
|
||||
LineChartSample(10, (context) => const LineChartSample10()),
|
||||
LineChartSample(11, (context) => const LineChartSample11()),
|
||||
LineChartSample(12, (context) => const LineChartSample12()),
|
||||
LineChartSample(13, (context) => const LineChartSample13()),
|
||||
],
|
||||
ChartType.bar: [
|
||||
BarChartSample(1, (context) => BarChartSample1()),
|
||||
BarChartSample(2, (context) => BarChartSample2()),
|
||||
BarChartSample(3, (context) => const BarChartSample3()),
|
||||
BarChartSample(4, (context) => BarChartSample4()),
|
||||
BarChartSample(5, (context) => const BarChartSample5()),
|
||||
BarChartSample(6, (context) => const BarChartSample6()),
|
||||
BarChartSample(7, (context) => BarChartSample7()),
|
||||
BarChartSample(8, (context) => BarChartSample8()),
|
||||
],
|
||||
ChartType.pie: [
|
||||
PieChartSample(1, (context) => const PieChartSample1()),
|
||||
PieChartSample(2, (context) => const PieChartSample2()),
|
||||
PieChartSample(3, (context) => const PieChartSample3()),
|
||||
],
|
||||
ChartType.scatter: [
|
||||
ScatterChartSample(1, (context) => ScatterChartSample1()),
|
||||
ScatterChartSample(2, (context) => const ScatterChartSample2()),
|
||||
],
|
||||
ChartType.radar: [
|
||||
RadarChartSample(1, (context) => RadarChartSample1()),
|
||||
],
|
||||
ChartType.candlestick: [
|
||||
CandlestickChartSample(1, (context) => const CandlestickChartSample1()),
|
||||
]
|
||||
};
|
||||
}
|
||||
366
example/lib/presentation/samples/line/line_chart_sample1.dart
Normal file
366
example/lib/presentation/samples/line/line_chart_sample1.dart
Normal file
@@ -0,0 +1,366 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class _LineChart extends StatelessWidget {
|
||||
const _LineChart({required this.isShowingMainData});
|
||||
|
||||
final bool isShowingMainData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LineChart(
|
||||
isShowingMainData ? sampleData1 : sampleData2,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
);
|
||||
}
|
||||
|
||||
LineChartData get sampleData1 => LineChartData(
|
||||
lineTouchData: lineTouchData1,
|
||||
gridData: gridData,
|
||||
titlesData: titlesData1,
|
||||
borderData: borderData,
|
||||
lineBarsData: lineBarsData1,
|
||||
minX: 0,
|
||||
maxX: 14,
|
||||
maxY: 4,
|
||||
minY: 0,
|
||||
);
|
||||
|
||||
LineChartData get sampleData2 => LineChartData(
|
||||
lineTouchData: lineTouchData2,
|
||||
gridData: gridData,
|
||||
titlesData: titlesData2,
|
||||
borderData: borderData,
|
||||
lineBarsData: lineBarsData2,
|
||||
minX: 0,
|
||||
maxX: 14,
|
||||
maxY: 6,
|
||||
minY: 0,
|
||||
);
|
||||
|
||||
LineTouchData get lineTouchData1 => LineTouchData(
|
||||
handleBuiltInTouches: true,
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
getTooltipColor: (touchedSpot) =>
|
||||
Colors.blueGrey.withValues(alpha: 0.8),
|
||||
),
|
||||
);
|
||||
|
||||
FlTitlesData get titlesData1 => FlTitlesData(
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: bottomTitles,
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: leftTitles(),
|
||||
),
|
||||
);
|
||||
|
||||
List<LineChartBarData> get lineBarsData1 => [
|
||||
lineChartBarData1_1,
|
||||
lineChartBarData1_2,
|
||||
lineChartBarData1_3,
|
||||
];
|
||||
|
||||
LineTouchData get lineTouchData2 => const LineTouchData(
|
||||
enabled: false,
|
||||
);
|
||||
|
||||
FlTitlesData get titlesData2 => FlTitlesData(
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: bottomTitles,
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: leftTitles(),
|
||||
),
|
||||
);
|
||||
|
||||
List<LineChartBarData> get lineBarsData2 => [
|
||||
lineChartBarData2_1,
|
||||
lineChartBarData2_2,
|
||||
lineChartBarData2_3,
|
||||
];
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 1:
|
||||
text = '1m';
|
||||
break;
|
||||
case 2:
|
||||
text = '2m';
|
||||
break;
|
||||
case 3:
|
||||
text = '3m';
|
||||
break;
|
||||
case 4:
|
||||
text = '5m';
|
||||
break;
|
||||
case 5:
|
||||
text = '6m';
|
||||
break;
|
||||
default:
|
||||
return Container();
|
||||
}
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(
|
||||
text,
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
SideTitles leftTitles() => SideTitles(
|
||||
getTitlesWidget: leftTitleWidgets,
|
||||
showTitles: true,
|
||||
interval: 1,
|
||||
reservedSize: 40,
|
||||
);
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
);
|
||||
Widget text;
|
||||
switch (value.toInt()) {
|
||||
case 2:
|
||||
text = const Text('SEPT', style: style);
|
||||
break;
|
||||
case 7:
|
||||
text = const Text('OCT', style: style);
|
||||
break;
|
||||
case 12:
|
||||
text = const Text('DEC', style: style);
|
||||
break;
|
||||
default:
|
||||
text = const Text('');
|
||||
break;
|
||||
}
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 10,
|
||||
child: text,
|
||||
);
|
||||
}
|
||||
|
||||
SideTitles get bottomTitles => SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 32,
|
||||
interval: 1,
|
||||
getTitlesWidget: bottomTitleWidgets,
|
||||
);
|
||||
|
||||
FlGridData get gridData => const FlGridData(show: false);
|
||||
|
||||
FlBorderData get borderData => FlBorderData(
|
||||
show: true,
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: AppColors.primary.withValues(alpha: 0.2), width: 4),
|
||||
left: const BorderSide(color: Colors.transparent),
|
||||
right: const BorderSide(color: Colors.transparent),
|
||||
top: const BorderSide(color: Colors.transparent),
|
||||
),
|
||||
);
|
||||
|
||||
LineChartBarData get lineChartBarData1_1 => LineChartBarData(
|
||||
isCurved: true,
|
||||
color: AppColors.contentColorGreen,
|
||||
barWidth: 8,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(show: false),
|
||||
belowBarData: BarAreaData(show: false),
|
||||
spots: const [
|
||||
FlSpot(1, 1),
|
||||
FlSpot(3, 1.5),
|
||||
FlSpot(5, 1.4),
|
||||
FlSpot(7, 3.4),
|
||||
FlSpot(10, 2),
|
||||
FlSpot(12, 2.2),
|
||||
FlSpot(13, 1.8),
|
||||
],
|
||||
);
|
||||
|
||||
LineChartBarData get lineChartBarData1_2 => LineChartBarData(
|
||||
isCurved: true,
|
||||
color: AppColors.contentColorPink,
|
||||
barWidth: 8,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(show: false),
|
||||
belowBarData: BarAreaData(
|
||||
show: false,
|
||||
color: AppColors.contentColorPink.withValues(alpha: 0),
|
||||
),
|
||||
spots: const [
|
||||
FlSpot(1, 1),
|
||||
FlSpot(3, 2.8),
|
||||
FlSpot(7, 1.2),
|
||||
FlSpot(10, 2.8),
|
||||
FlSpot(12, 2.6),
|
||||
FlSpot(13, 3.9),
|
||||
],
|
||||
);
|
||||
|
||||
LineChartBarData get lineChartBarData1_3 => LineChartBarData(
|
||||
isCurved: true,
|
||||
color: AppColors.contentColorCyan,
|
||||
barWidth: 8,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(show: false),
|
||||
belowBarData: BarAreaData(show: false),
|
||||
spots: const [
|
||||
FlSpot(1, 2.8),
|
||||
FlSpot(3, 1.9),
|
||||
FlSpot(6, 3),
|
||||
FlSpot(10, 1.3),
|
||||
FlSpot(13, 2.5),
|
||||
],
|
||||
);
|
||||
|
||||
LineChartBarData get lineChartBarData2_1 => LineChartBarData(
|
||||
isCurved: true,
|
||||
curveSmoothness: 0,
|
||||
color: AppColors.contentColorGreen.withValues(alpha: 0.5),
|
||||
barWidth: 4,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(show: false),
|
||||
belowBarData: BarAreaData(show: false),
|
||||
spots: const [
|
||||
FlSpot(1, 1),
|
||||
FlSpot(3, 4),
|
||||
FlSpot(5, 1.8),
|
||||
FlSpot(7, 5),
|
||||
FlSpot(10, 2),
|
||||
FlSpot(12, 2.2),
|
||||
FlSpot(13, 1.8),
|
||||
],
|
||||
);
|
||||
|
||||
LineChartBarData get lineChartBarData2_2 => LineChartBarData(
|
||||
isCurved: true,
|
||||
color: AppColors.contentColorPink.withValues(alpha: 0.5),
|
||||
barWidth: 4,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(show: false),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
color: AppColors.contentColorPink.withValues(alpha: 0.2),
|
||||
),
|
||||
spots: const [
|
||||
FlSpot(1, 1),
|
||||
FlSpot(3, 2.8),
|
||||
FlSpot(7, 1.2),
|
||||
FlSpot(10, 2.8),
|
||||
FlSpot(12, 2.6),
|
||||
FlSpot(13, 3.9),
|
||||
],
|
||||
);
|
||||
|
||||
LineChartBarData get lineChartBarData2_3 => LineChartBarData(
|
||||
isCurved: true,
|
||||
curveSmoothness: 0,
|
||||
color: AppColors.contentColorCyan.withValues(alpha: 0.5),
|
||||
barWidth: 2,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(show: true),
|
||||
belowBarData: BarAreaData(show: false),
|
||||
spots: const [
|
||||
FlSpot(1, 3.8),
|
||||
FlSpot(3, 1.9),
|
||||
FlSpot(6, 5),
|
||||
FlSpot(10, 3.3),
|
||||
FlSpot(13, 4.5),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
class LineChartSample1 extends StatefulWidget {
|
||||
const LineChartSample1({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => LineChartSample1State();
|
||||
}
|
||||
|
||||
class LineChartSample1State extends State<LineChartSample1> {
|
||||
late bool isShowingMainData;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
isShowingMainData = true;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.23,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
height: 37,
|
||||
),
|
||||
const Text(
|
||||
'Monthly Sales',
|
||||
style: TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 2,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 37,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 16, left: 6),
|
||||
child: _LineChart(isShowingMainData: isShowingMainData),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
Icons.refresh,
|
||||
color:
|
||||
Colors.white.withValues(alpha: isShowingMainData ? 1.0 : 0.5),
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
isShowingMainData = !isShowingMainData;
|
||||
});
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
146
example/lib/presentation/samples/line/line_chart_sample10.dart
Normal file
146
example/lib/presentation/samples/line/line_chart_sample10.dart
Normal file
@@ -0,0 +1,146 @@
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LineChartSample10 extends StatefulWidget {
|
||||
const LineChartSample10({super.key});
|
||||
|
||||
final Color sinColor = AppColors.contentColorBlue;
|
||||
final Color cosColor = AppColors.contentColorPink;
|
||||
|
||||
@override
|
||||
State<LineChartSample10> createState() => _LineChartSample10State();
|
||||
}
|
||||
|
||||
class _LineChartSample10State extends State<LineChartSample10> {
|
||||
final limitCount = 100;
|
||||
final sinPoints = <FlSpot>[];
|
||||
final cosPoints = <FlSpot>[];
|
||||
|
||||
double xValue = 0;
|
||||
double step = 0.05;
|
||||
|
||||
late Timer timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
timer = Timer.periodic(const Duration(milliseconds: 40), (timer) {
|
||||
while (sinPoints.length > limitCount) {
|
||||
sinPoints.removeAt(0);
|
||||
cosPoints.removeAt(0);
|
||||
}
|
||||
setState(() {
|
||||
sinPoints.add(FlSpot(xValue, math.sin(xValue)));
|
||||
cosPoints.add(FlSpot(xValue, math.cos(xValue)));
|
||||
});
|
||||
xValue += step;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return cosPoints.isNotEmpty
|
||||
? Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
'x: ${xValue.toStringAsFixed(1)}',
|
||||
style: const TextStyle(
|
||||
color: AppColors.mainTextColor2,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'sin: ${sinPoints.last.y.toStringAsFixed(1)}',
|
||||
style: TextStyle(
|
||||
color: widget.sinColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'cos: ${cosPoints.last.y.toStringAsFixed(1)}',
|
||||
style: TextStyle(
|
||||
color: widget.cosColor,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 24.0),
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
minY: -1,
|
||||
maxY: 1,
|
||||
minX: sinPoints.first.x,
|
||||
maxX: sinPoints.last.x,
|
||||
lineTouchData: const LineTouchData(enabled: false),
|
||||
clipData: const FlClipData.all(),
|
||||
gridData: const FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
lineBarsData: [
|
||||
sinLine(sinPoints),
|
||||
cosLine(cosPoints),
|
||||
],
|
||||
titlesData: const FlTitlesData(
|
||||
show: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
)
|
||||
: Container();
|
||||
}
|
||||
|
||||
LineChartBarData sinLine(List<FlSpot> points) {
|
||||
return LineChartBarData(
|
||||
spots: points,
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
),
|
||||
gradient: LinearGradient(
|
||||
colors: [widget.sinColor.withValues(alpha: 0), widget.sinColor],
|
||||
stops: const [0.1, 1.0],
|
||||
),
|
||||
barWidth: 4,
|
||||
isCurved: false,
|
||||
);
|
||||
}
|
||||
|
||||
LineChartBarData cosLine(List<FlSpot> points) {
|
||||
return LineChartBarData(
|
||||
spots: points,
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
),
|
||||
gradient: LinearGradient(
|
||||
colors: [widget.cosColor.withValues(alpha: 0), widget.cosColor],
|
||||
stops: const [0.1, 1.0],
|
||||
),
|
||||
barWidth: 4,
|
||||
isCurved: false,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
timer.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
202
example/lib/presentation/samples/line/line_chart_sample11.dart
Normal file
202
example/lib/presentation/samples/line/line_chart_sample11.dart
Normal file
@@ -0,0 +1,202 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LineChartSample11 extends StatefulWidget {
|
||||
const LineChartSample11({super.key});
|
||||
|
||||
@override
|
||||
State<LineChartSample11> createState() => _LineChartSample11State();
|
||||
}
|
||||
|
||||
class _LineChartSample11State extends State<LineChartSample11> {
|
||||
var baselineX = 0.0;
|
||||
var baselineY = 0.0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 18.0,
|
||||
right: 18.0,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
RotatedBox(
|
||||
quarterTurns: 1,
|
||||
child: Slider(
|
||||
value: baselineY,
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
baselineY = newValue;
|
||||
});
|
||||
},
|
||||
min: -10,
|
||||
max: 10,
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: _Chart(
|
||||
baselineX,
|
||||
(20 - (baselineY + 10)) - 10,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Slider(
|
||||
value: baselineX,
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
baselineX = newValue;
|
||||
});
|
||||
},
|
||||
min: -10,
|
||||
max: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Chart extends StatelessWidget {
|
||||
final double baselineX;
|
||||
final double baselineY;
|
||||
|
||||
const _Chart(this.baselineX, this.baselineY) : super();
|
||||
|
||||
Widget getHorizontalTitles(value, TitleMeta meta) {
|
||||
TextStyle style;
|
||||
if ((value - baselineX).abs() <= 0.1) {
|
||||
style = const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
} else {
|
||||
style = const TextStyle(
|
||||
color: Colors.white60,
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Text(meta.formattedValue, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
Widget getVerticalTitles(value, TitleMeta meta) {
|
||||
TextStyle style;
|
||||
if ((value - baselineY).abs() <= 0.1) {
|
||||
style = const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
} else {
|
||||
style = const TextStyle(
|
||||
color: Colors.white60,
|
||||
fontSize: 14,
|
||||
);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
child: Text(meta.formattedValue, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
FlLine getHorizontalVerticalLine(double value) {
|
||||
if ((value - baselineY).abs() <= 0.1) {
|
||||
return const FlLine(
|
||||
color: Colors.white70,
|
||||
strokeWidth: 1,
|
||||
dashArray: [8, 4],
|
||||
);
|
||||
} else {
|
||||
return const FlLine(
|
||||
color: Colors.blueGrey,
|
||||
strokeWidth: 0.4,
|
||||
dashArray: [8, 4],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
FlLine getVerticalVerticalLine(double value) {
|
||||
if ((value - baselineX).abs() <= 0.1) {
|
||||
return const FlLine(
|
||||
color: Colors.white70,
|
||||
strokeWidth: 1,
|
||||
dashArray: [8, 4],
|
||||
);
|
||||
} else {
|
||||
return const FlLine(
|
||||
color: Colors.blueGrey,
|
||||
strokeWidth: 0.4,
|
||||
dashArray: [8, 4],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LineChart(
|
||||
LineChartData(
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: [],
|
||||
),
|
||||
],
|
||||
titlesData: FlTitlesData(
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: getVerticalTitles,
|
||||
reservedSize: 36,
|
||||
),
|
||||
),
|
||||
topTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: getHorizontalTitles,
|
||||
reservedSize: 32),
|
||||
),
|
||||
rightTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: getVerticalTitles,
|
||||
reservedSize: 36,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: getHorizontalTitles,
|
||||
reservedSize: 32),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawHorizontalLine: true,
|
||||
drawVerticalLine: true,
|
||||
getDrawingHorizontalLine: getHorizontalVerticalLine,
|
||||
getDrawingVerticalLine: getVerticalVerticalLine,
|
||||
),
|
||||
minY: -10,
|
||||
maxY: 10,
|
||||
baselineY: baselineY,
|
||||
minX: -10,
|
||||
maxX: 10,
|
||||
baselineX: baselineX,
|
||||
),
|
||||
duration: Duration.zero,
|
||||
);
|
||||
}
|
||||
}
|
||||
416
example/lib/presentation/samples/line/line_chart_sample12.dart
Normal file
416
example/lib/presentation/samples/line/line_chart_sample12.dart
Normal file
@@ -0,0 +1,416 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/presentation_utils.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_colors.dart';
|
||||
import 'package:fl_chart_app/util/extensions/color_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class LineChartSample12 extends StatefulWidget {
|
||||
const LineChartSample12({super.key});
|
||||
|
||||
@override
|
||||
State<LineChartSample12> createState() => _LineChartSample12State();
|
||||
}
|
||||
|
||||
class _LineChartSample12State extends State<LineChartSample12> {
|
||||
List<(DateTime, double)>? _bitcoinPriceHistory;
|
||||
late TransformationController _transformationController;
|
||||
bool _isPanEnabled = true;
|
||||
bool _isScaleEnabled = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_reloadData();
|
||||
_transformationController = TransformationController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _reloadData() async {
|
||||
final dataStr = await rootBundle.loadString(
|
||||
'assets/data/btc_last_year_price.json',
|
||||
);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final json = jsonDecode(dataStr) as Map<String, dynamic>;
|
||||
setState(() {
|
||||
_bitcoinPriceHistory = (json['prices'] as List).map((item) {
|
||||
final timestamp = item[0] as int;
|
||||
final price = item[1] as double;
|
||||
return (DateTime.fromMillisecondsSinceEpoch(timestamp), price);
|
||||
}).toList();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const leftReservedSize = 52.0;
|
||||
return Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final width = constraints.maxWidth;
|
||||
return width >= 380
|
||||
? Row(
|
||||
children: [
|
||||
const SizedBox(width: leftReservedSize),
|
||||
const _ChartTitle(),
|
||||
const Spacer(),
|
||||
Center(
|
||||
child: _TransformationButtons(
|
||||
controller: _transformationController,
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
const _ChartTitle(),
|
||||
const SizedBox(height: 16),
|
||||
_TransformationButtons(
|
||||
controller: _transformationController,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
spacing: 16,
|
||||
children: [
|
||||
const Text('Pan'),
|
||||
Switch(
|
||||
value: _isPanEnabled,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_isPanEnabled = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
const Text('Scale'),
|
||||
Switch(
|
||||
value: _isScaleEnabled,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_isScaleEnabled = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 0.0,
|
||||
right: 18.0,
|
||||
),
|
||||
child: LineChart(
|
||||
transformationConfig: FlTransformationConfig(
|
||||
scaleAxis: FlScaleAxis.horizontal,
|
||||
minScale: 1.0,
|
||||
maxScale: 25.0,
|
||||
panEnabled: _isPanEnabled,
|
||||
scaleEnabled: _isScaleEnabled,
|
||||
transformationController: _transformationController,
|
||||
),
|
||||
LineChartData(
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: _bitcoinPriceHistory?.asMap().entries.map((e) {
|
||||
final index = e.key;
|
||||
final item = e.value;
|
||||
final value = item.$2;
|
||||
return FlSpot(index.toDouble(), value);
|
||||
}).toList() ??
|
||||
[],
|
||||
dotData: const FlDotData(show: false),
|
||||
color: AppColors.contentColorYellow,
|
||||
barWidth: 1,
|
||||
shadow: const Shadow(
|
||||
color: AppColors.contentColorYellow,
|
||||
blurRadius: 2,
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
AppColors.contentColorYellow.withValues(alpha: 0.2),
|
||||
AppColors.contentColorYellow.withValues(alpha: 0.0),
|
||||
],
|
||||
stops: const [0.5, 1.0],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
lineTouchData: LineTouchData(
|
||||
touchSpotThreshold: 5,
|
||||
getTouchLineStart: (_, __) => -double.infinity,
|
||||
getTouchLineEnd: (_, __) => double.infinity,
|
||||
getTouchedSpotIndicator:
|
||||
(LineChartBarData barData, List<int> spotIndexes) {
|
||||
return spotIndexes.map((spotIndex) {
|
||||
return TouchedSpotIndicatorData(
|
||||
const FlLine(
|
||||
color: AppColors.contentColorRed,
|
||||
strokeWidth: 1.5,
|
||||
dashArray: [8, 2],
|
||||
),
|
||||
FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 6,
|
||||
color: AppColors.contentColorYellow,
|
||||
strokeWidth: 0,
|
||||
strokeColor: AppColors.contentColorYellow,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
getTooltipItems: (List<LineBarSpot> touchedBarSpots) {
|
||||
return touchedBarSpots.map((barSpot) {
|
||||
final price = barSpot.y;
|
||||
final date =
|
||||
_bitcoinPriceHistory![barSpot.x.toInt()].$1;
|
||||
return LineTooltipItem(
|
||||
'',
|
||||
const TextStyle(
|
||||
color: AppColors.contentColorBlack,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${date.year}/${date.month}/${date.day}',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorGreen.darken(20),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: '\n${AppUtils.getFormattedCurrency(
|
||||
context,
|
||||
price,
|
||||
noDecimals: true,
|
||||
)}',
|
||||
style: const TextStyle(
|
||||
color: AppColors.contentColorYellow,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
getTooltipColor: (LineBarSpot barSpot) =>
|
||||
AppColors.contentColorBlack,
|
||||
),
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
leftTitles: const AxisTitles(
|
||||
drawBelowEverything: true,
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: leftReservedSize,
|
||||
maxIncluded: false,
|
||||
minIncluded: false,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 38,
|
||||
maxIncluded: false,
|
||||
getTitlesWidget: (double value, TitleMeta meta) {
|
||||
final date = _bitcoinPriceHistory![value.toInt()].$1;
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Transform.rotate(
|
||||
angle: -45 * 3.14 / 180,
|
||||
child: Text(
|
||||
'${date.month}/${date.day}',
|
||||
style: const TextStyle(
|
||||
color: AppColors.contentColorGreen,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
duration: Duration.zero,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_transformationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _ChartTitle extends StatelessWidget {
|
||||
const _ChartTitle();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 14),
|
||||
Text(
|
||||
'Bitcoin Price History',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorYellow,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'2023/12/19 - 2024/12/17',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorGreen,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 14),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TransformationButtons extends StatelessWidget {
|
||||
const _TransformationButtons({
|
||||
required this.controller,
|
||||
});
|
||||
|
||||
final TransformationController controller;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Tooltip(
|
||||
message: 'Zoom in',
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.add,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: _transformationZoomIn,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Tooltip(
|
||||
message: 'Move left',
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: _transformationMoveLeft,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Reset zoom',
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.refresh,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: _transformationReset,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Move right',
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: _transformationMoveRight,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Zoom out',
|
||||
child: IconButton(
|
||||
icon: const Icon(
|
||||
Icons.minimize,
|
||||
size: 16,
|
||||
),
|
||||
onPressed: _transformationZoomOut,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _transformationReset() {
|
||||
controller.value = Matrix4.identity();
|
||||
}
|
||||
|
||||
void _transformationZoomIn() {
|
||||
controller.value *= Matrix4.diagonal3Values(
|
||||
1.1,
|
||||
1.1,
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
void _transformationMoveLeft() {
|
||||
controller.value *= Matrix4.translationValues(
|
||||
20,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
void _transformationMoveRight() {
|
||||
controller.value *= Matrix4.translationValues(
|
||||
-20,
|
||||
0,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
void _transformationZoomOut() {
|
||||
controller.value *= Matrix4.diagonal3Values(
|
||||
0.9,
|
||||
0.9,
|
||||
1,
|
||||
);
|
||||
}
|
||||
}
|
||||
494
example/lib/presentation/samples/line/line_chart_sample13.dart
Normal file
494
example/lib/presentation/samples/line/line_chart_sample13.dart
Normal file
@@ -0,0 +1,494 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/util/app_utils.dart';
|
||||
import 'package:fl_chart_app/util/csv_parser.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class LineChartSample13 extends StatefulWidget {
|
||||
const LineChartSample13({super.key});
|
||||
|
||||
@override
|
||||
State<LineChartSample13> createState() => _LineChartSample13State();
|
||||
}
|
||||
|
||||
class _LineChartSample13State extends State<LineChartSample13> {
|
||||
List<List<_WeatherData>>? monthlyWeatherData;
|
||||
int _currentMonthIndex = 0;
|
||||
late final List<String> monthsNames;
|
||||
|
||||
final int minDays = 1;
|
||||
final int maxDays = 31;
|
||||
late final double overallMinTemp;
|
||||
late final double overallMaxTemp;
|
||||
|
||||
int _interactedSpotIndex = -1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
monthsNames = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
_loadWeatherData();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void _loadWeatherData() async {
|
||||
final data =
|
||||
await rootBundle.loadString('assets/data/amsterdam_2024_weather.csv');
|
||||
final rows = CsvParser.parse(data);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
final allWeatherData =
|
||||
rows.skip(1).map((row) => _WeatherData.fromCsv(row)).toList();
|
||||
monthlyWeatherData = List.generate(12, (index) {
|
||||
final month = index + 1;
|
||||
return allWeatherData
|
||||
.where((element) => element.datetime.month == month)
|
||||
.toList();
|
||||
});
|
||||
overallMinTemp = allWeatherData
|
||||
.map((e) => e.temp)
|
||||
.reduce((value, element) => value < element ? value : element);
|
||||
overallMaxTemp = allWeatherData
|
||||
.map((e) => e.temp)
|
||||
.reduce((value, element) => value > element ? value : element);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 18),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text(
|
||||
'Amsterdam Weather 2024',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorOrange,
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Tooltip(
|
||||
message: 'Source: visualcrossing.com',
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
AppUtils().tryToLaunchUrl(
|
||||
'https://www.visualcrossing.com/weather-history/Amsterdam,Netherlands/metric/2024-01-01/2024-12-31',
|
||||
);
|
||||
},
|
||||
icon: const Icon(
|
||||
Icons.info_outline_rounded,
|
||||
color: AppColors.contentColorOrange,
|
||||
size: 18,
|
||||
)),
|
||||
)
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: IconButton(
|
||||
onPressed: _canGoPrevious ? _previousMonth : null,
|
||||
icon: const Icon(Icons.navigate_before_rounded),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 92,
|
||||
child: Text(
|
||||
monthsNames[_currentMonthIndex],
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: AppColors.contentColorBlue,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: IconButton(
|
||||
onPressed: _canGoNext ? _nextMonth : null,
|
||||
icon: const Icon(Icons.navigate_next_rounded),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.4,
|
||||
child: Stack(
|
||||
children: [
|
||||
if (monthlyWeatherData != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 0.0,
|
||||
right: 18.0,
|
||||
),
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
minY: overallMinTemp - 5,
|
||||
maxY: overallMaxTemp + 5,
|
||||
minX: 0,
|
||||
maxX: 31,
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: monthlyWeatherData![_currentMonthIndex]
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) {
|
||||
final index = e.key;
|
||||
final item = e.value;
|
||||
final value = item.temp;
|
||||
return FlSpot(
|
||||
index.toDouble(),
|
||||
value,
|
||||
yError: FlErrorRange(
|
||||
lowerBy: (item.tempmin - value).abs(),
|
||||
upperBy: item.tempmax - value,
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
isCurved: false,
|
||||
dotData: const FlDotData(show: false),
|
||||
color: AppColors.contentColorBlue,
|
||||
barWidth: 1,
|
||||
errorIndicatorData: FlErrorIndicatorData(
|
||||
show: true,
|
||||
painter: _errorPainter,
|
||||
),
|
||||
),
|
||||
],
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawHorizontalLine: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: 5,
|
||||
getDrawingHorizontalLine: _horizontalGridLines,
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
drawBelowEverything: true,
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
maxIncluded: false,
|
||||
minIncluded: false,
|
||||
reservedSize: 40,
|
||||
getTitlesWidget: (double value, TitleMeta meta) {
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(
|
||||
'${meta.formattedValue}°',
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
axisNameWidget: Container(
|
||||
margin: const EdgeInsets.only(bottom: 20),
|
||||
child: const Text(
|
||||
'Day of month',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorGreen,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
),
|
||||
axisNameSize: 40,
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 38,
|
||||
maxIncluded: false,
|
||||
interval: 1,
|
||||
getTitlesWidget: _bottomTitles,
|
||||
),
|
||||
),
|
||||
),
|
||||
lineTouchData: LineTouchData(
|
||||
enabled: true,
|
||||
handleBuiltInTouches: false,
|
||||
touchCallback: _touchCallback,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (monthlyWeatherData == null)
|
||||
const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
bool get _canGoNext => _currentMonthIndex < 11;
|
||||
|
||||
bool get _canGoPrevious => _currentMonthIndex > 0;
|
||||
|
||||
void _previousMonth() {
|
||||
if (!_canGoPrevious) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_currentMonthIndex--;
|
||||
});
|
||||
}
|
||||
|
||||
void _nextMonth() {
|
||||
if (!_canGoNext) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_currentMonthIndex++;
|
||||
});
|
||||
}
|
||||
|
||||
FlSpotErrorRangePainter _errorPainter(
|
||||
LineChartSpotErrorRangeCallbackInput input,
|
||||
) =>
|
||||
FlSimpleErrorPainter(
|
||||
lineWidth: 1.0,
|
||||
lineColor: _interactedSpotIndex == input.spotIndex
|
||||
? Colors.white
|
||||
: Colors.white38,
|
||||
showErrorTexts: _interactedSpotIndex == input.spotIndex,
|
||||
);
|
||||
|
||||
FlLine _horizontalGridLines(double value) {
|
||||
final isZero = value == 0.0;
|
||||
return FlLine(
|
||||
color: isZero ? Colors.white38 : Colors.blueGrey,
|
||||
strokeWidth: isZero ? 0.8 : 0.4,
|
||||
dashArray: isZero ? null : [8, 4],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _bottomTitles(double value, TitleMeta meta) {
|
||||
final day = value.toInt() + 1;
|
||||
|
||||
final isDayHovered = _interactedSpotIndex == day - 1;
|
||||
|
||||
final isImportantToShow = day % 5 == 0 || day == 1;
|
||||
|
||||
if (!isImportantToShow && !isDayHovered) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(
|
||||
day.toString(),
|
||||
style: TextStyle(
|
||||
color: isDayHovered
|
||||
? AppColors.contentColorWhite
|
||||
: AppColors.contentColorGreen,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_touchCallback(FlTouchEvent event, LineTouchResponse? touchResponse) {
|
||||
if (!event.isInterestedForInteractions ||
|
||||
touchResponse?.lineBarSpots == null ||
|
||||
touchResponse!.lineBarSpots!.isEmpty) {
|
||||
setState(() {
|
||||
_interactedSpotIndex = -1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_interactedSpotIndex = touchResponse.lineBarSpots!.first.spotIndex;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _WeatherData with EquatableMixin {
|
||||
final String name;
|
||||
final DateTime datetime;
|
||||
final double tempmax;
|
||||
final double tempmin;
|
||||
final double temp;
|
||||
final double feelslikemax;
|
||||
final double feelslikemin;
|
||||
final double feelslike;
|
||||
final double dew;
|
||||
final double humidity;
|
||||
final double precip;
|
||||
final double precipprob;
|
||||
final double precipcover;
|
||||
final String preciptype;
|
||||
final double snow;
|
||||
final double snowdepth;
|
||||
final double windgust;
|
||||
final double windspeed;
|
||||
final double winddir;
|
||||
final double sealevelpressure;
|
||||
final double cloudcover;
|
||||
final double visibility;
|
||||
final double solarradiation;
|
||||
final double solarenergy;
|
||||
final double uvindex;
|
||||
final double severerisk;
|
||||
final DateTime sunrise;
|
||||
final DateTime sunset;
|
||||
final double moonphase;
|
||||
final String conditions;
|
||||
final String description;
|
||||
final String icon;
|
||||
final String stations;
|
||||
|
||||
const _WeatherData({
|
||||
required this.name,
|
||||
required this.datetime,
|
||||
required this.tempmax,
|
||||
required this.tempmin,
|
||||
required this.temp,
|
||||
required this.feelslikemax,
|
||||
required this.feelslikemin,
|
||||
required this.feelslike,
|
||||
required this.dew,
|
||||
required this.humidity,
|
||||
required this.precip,
|
||||
required this.precipprob,
|
||||
required this.precipcover,
|
||||
required this.preciptype,
|
||||
required this.snow,
|
||||
required this.snowdepth,
|
||||
required this.windgust,
|
||||
required this.windspeed,
|
||||
required this.winddir,
|
||||
required this.sealevelpressure,
|
||||
required this.cloudcover,
|
||||
required this.visibility,
|
||||
required this.solarradiation,
|
||||
required this.solarenergy,
|
||||
required this.uvindex,
|
||||
required this.severerisk,
|
||||
required this.sunrise,
|
||||
required this.sunset,
|
||||
required this.moonphase,
|
||||
required this.conditions,
|
||||
required this.description,
|
||||
required this.icon,
|
||||
required this.stations,
|
||||
});
|
||||
|
||||
// parse from csv row
|
||||
// name,datetime,tempmax,tempmin,temp,feelslikemax,feelslikemin,feelslike,dew,humidity,precip,precipprob,precipcover,preciptype,snow,snowdepth,windgust,windspeed,winddir,sealevelpressure,cloudcover,visibility,solarradiation,solarenergy,uvindex,severerisk,sunrise,sunset,moonphase,conditions,description,icon,stations
|
||||
// "Amsterdam,Netherlands",2024-01-01,9.1,6.4,8,5.3,2.5,4.1,5.1,82.4,14.26,100,37.5,rain,0,0,53.9,40.2,225.9,1000.1,88.7,20.5,20.6,1.8,2,,2024-01-01T08:50:34,2024-01-01T16:37:06,0.68,"Rain, Partially cloudy",Partly cloudy throughout the day with a chance of rain throughout the day.,rain,"06260099999,D3248,06348099999,06249099999,C0449,06240099999,06269099999,06257099999,06344099999"
|
||||
factory _WeatherData.fromCsv(List<String> row) => _WeatherData(
|
||||
name: row[0],
|
||||
datetime: DateTime.parse(row[1]),
|
||||
tempmax: double.parse(row[2]),
|
||||
tempmin: double.parse(row[3]),
|
||||
temp: double.parse(row[4]),
|
||||
feelslikemax: double.parse(row[5]),
|
||||
feelslikemin: double.parse(row[6]),
|
||||
feelslike: double.parse(row[7]),
|
||||
dew: double.parse(row[8]),
|
||||
humidity: double.parse(row[9]),
|
||||
precip: double.parse(row[10]),
|
||||
precipprob: double.parse(row[11]),
|
||||
precipcover: double.parse(row[12]),
|
||||
preciptype: row[13],
|
||||
snow: double.parse(row[14]),
|
||||
snowdepth: double.parse(row[15]),
|
||||
windgust: double.parse(row[16]),
|
||||
windspeed: double.parse(row[17]),
|
||||
winddir: double.parse(row[18]),
|
||||
sealevelpressure: double.parse(row[19]),
|
||||
cloudcover: double.parse(row[20]),
|
||||
visibility: double.parse(row[21]),
|
||||
solarradiation: double.parse(row[22]),
|
||||
solarenergy: double.parse(row[23]),
|
||||
uvindex: double.parse(row[24]),
|
||||
severerisk: row[25].isEmpty ? 0 : double.parse(row[25]),
|
||||
sunrise: DateTime.parse(row[26]),
|
||||
sunset: DateTime.parse(row[27]),
|
||||
moonphase: double.parse(row[28]),
|
||||
conditions: row[29],
|
||||
description: row[30],
|
||||
icon: row[31],
|
||||
stations: row[32],
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
name,
|
||||
datetime,
|
||||
tempmax,
|
||||
tempmin,
|
||||
temp,
|
||||
feelslikemax,
|
||||
feelslikemin,
|
||||
feelslike,
|
||||
dew,
|
||||
humidity,
|
||||
precip,
|
||||
precipprob,
|
||||
precipcover,
|
||||
preciptype,
|
||||
snow,
|
||||
snowdepth,
|
||||
windgust,
|
||||
windspeed,
|
||||
winddir,
|
||||
sealevelpressure,
|
||||
cloudcover,
|
||||
visibility,
|
||||
solarradiation,
|
||||
solarenergy,
|
||||
uvindex,
|
||||
severerisk,
|
||||
sunrise,
|
||||
sunset,
|
||||
moonphase,
|
||||
conditions,
|
||||
description,
|
||||
icon,
|
||||
stations,
|
||||
];
|
||||
}
|
||||
294
example/lib/presentation/samples/line/line_chart_sample2.dart
Normal file
294
example/lib/presentation/samples/line/line_chart_sample2.dart
Normal file
@@ -0,0 +1,294 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LineChartSample2 extends StatefulWidget {
|
||||
const LineChartSample2({super.key});
|
||||
|
||||
@override
|
||||
State<LineChartSample2> createState() => _LineChartSample2State();
|
||||
}
|
||||
|
||||
class _LineChartSample2State extends State<LineChartSample2> {
|
||||
List<Color> gradientColors = [
|
||||
AppColors.contentColorCyan,
|
||||
AppColors.contentColorBlue,
|
||||
];
|
||||
|
||||
bool showAvg = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: 1.70,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 18,
|
||||
left: 12,
|
||||
top: 24,
|
||||
bottom: 12,
|
||||
),
|
||||
child: LineChart(
|
||||
showAvg ? avgData() : mainData(),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: 60,
|
||||
height: 34,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
showAvg = !showAvg;
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
'avg',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: showAvg
|
||||
? Colors.white.withValues(alpha: 0.5)
|
||||
: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
);
|
||||
Widget text;
|
||||
switch (value.toInt()) {
|
||||
case 2:
|
||||
text = const Text('MAR', style: style);
|
||||
break;
|
||||
case 5:
|
||||
text = const Text('JUN', style: style);
|
||||
break;
|
||||
case 8:
|
||||
text = const Text('SEP', style: style);
|
||||
break;
|
||||
default:
|
||||
text = const Text('', style: style);
|
||||
break;
|
||||
}
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: text,
|
||||
);
|
||||
}
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 15,
|
||||
);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 1:
|
||||
text = '10K';
|
||||
break;
|
||||
case 3:
|
||||
text = '30k';
|
||||
break;
|
||||
case 5:
|
||||
text = '50k';
|
||||
break;
|
||||
default:
|
||||
return Container();
|
||||
}
|
||||
|
||||
return Text(text, style: style, textAlign: TextAlign.left);
|
||||
}
|
||||
|
||||
LineChartData mainData() {
|
||||
return LineChartData(
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: true,
|
||||
horizontalInterval: 1,
|
||||
verticalInterval: 1,
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return const FlLine(
|
||||
color: AppColors.mainGridLineColor,
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
getDrawingVerticalLine: (value) {
|
||||
return const FlLine(
|
||||
color: AppColors.mainGridLineColor,
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
interval: 1,
|
||||
getTitlesWidget: bottomTitleWidgets,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
interval: 1,
|
||||
getTitlesWidget: leftTitleWidgets,
|
||||
reservedSize: 42,
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: Border.all(color: const Color(0xff37434d)),
|
||||
),
|
||||
minX: 0,
|
||||
maxX: 11,
|
||||
minY: 0,
|
||||
maxY: 6,
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: const [
|
||||
FlSpot(0, 3),
|
||||
FlSpot(2.6, 2),
|
||||
FlSpot(4.9, 5),
|
||||
FlSpot(6.8, 3.1),
|
||||
FlSpot(8, 4),
|
||||
FlSpot(9.5, 3),
|
||||
FlSpot(11, 4),
|
||||
],
|
||||
isCurved: true,
|
||||
gradient: LinearGradient(
|
||||
colors: gradientColors,
|
||||
),
|
||||
barWidth: 5,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: gradientColors
|
||||
.map((color) => color.withValues(alpha: 0.3))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
LineChartData avgData() {
|
||||
return LineChartData(
|
||||
lineTouchData: const LineTouchData(enabled: false),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawHorizontalLine: true,
|
||||
verticalInterval: 1,
|
||||
horizontalInterval: 1,
|
||||
getDrawingVerticalLine: (value) {
|
||||
return const FlLine(
|
||||
color: Color(0xff37434d),
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
getDrawingHorizontalLine: (value) {
|
||||
return const FlLine(
|
||||
color: Color(0xff37434d),
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
getTitlesWidget: bottomTitleWidgets,
|
||||
interval: 1,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: leftTitleWidgets,
|
||||
reservedSize: 42,
|
||||
interval: 1,
|
||||
),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: Border.all(color: const Color(0xff37434d)),
|
||||
),
|
||||
minX: 0,
|
||||
maxX: 11,
|
||||
minY: 0,
|
||||
maxY: 6,
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: const [
|
||||
FlSpot(0, 3.44),
|
||||
FlSpot(2.6, 3.44),
|
||||
FlSpot(4.9, 3.44),
|
||||
FlSpot(6.8, 3.44),
|
||||
FlSpot(8, 3.44),
|
||||
FlSpot(9.5, 3.44),
|
||||
FlSpot(11, 3.44),
|
||||
],
|
||||
isCurved: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
ColorTween(begin: gradientColors[0], end: gradientColors[1])
|
||||
.lerp(0.2)!,
|
||||
ColorTween(begin: gradientColors[0], end: gradientColors[1])
|
||||
.lerp(0.2)!,
|
||||
],
|
||||
),
|
||||
barWidth: 5,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
ColorTween(begin: gradientColors[0], end: gradientColors[1])
|
||||
.lerp(0.2)!
|
||||
.withValues(alpha: 0.1),
|
||||
ColorTween(begin: gradientColors[0], end: gradientColors[1])
|
||||
.lerp(0.2)!
|
||||
.withValues(alpha: 0.1),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
451
example/lib/presentation/samples/line/line_chart_sample3.dart
Normal file
451
example/lib/presentation/samples/line/line_chart_sample3.dart
Normal file
@@ -0,0 +1,451 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LineChartSample3 extends StatefulWidget {
|
||||
LineChartSample3({
|
||||
super.key,
|
||||
Color? lineColor,
|
||||
Color? indicatorLineColor,
|
||||
Color? indicatorTouchedLineColor,
|
||||
Color? indicatorSpotStrokeColor,
|
||||
Color? indicatorTouchedSpotStrokeColor,
|
||||
Color? bottomTextColor,
|
||||
Color? bottomTouchedTextColor,
|
||||
Color? averageLineColor,
|
||||
Color? tooltipBgColor,
|
||||
Color? tooltipTextColor,
|
||||
}) : lineColor = lineColor ?? AppColors.contentColorRed,
|
||||
indicatorLineColor = indicatorLineColor ??
|
||||
AppColors.contentColorYellow.withValues(alpha: 0.2),
|
||||
indicatorTouchedLineColor =
|
||||
indicatorTouchedLineColor ?? AppColors.contentColorYellow,
|
||||
indicatorSpotStrokeColor = indicatorSpotStrokeColor ??
|
||||
AppColors.contentColorYellow.withValues(alpha: 0.5),
|
||||
indicatorTouchedSpotStrokeColor =
|
||||
indicatorTouchedSpotStrokeColor ?? AppColors.contentColorYellow,
|
||||
bottomTextColor = bottomTextColor ??
|
||||
AppColors.contentColorYellow.withValues(alpha: 0.2),
|
||||
bottomTouchedTextColor =
|
||||
bottomTouchedTextColor ?? AppColors.contentColorYellow,
|
||||
averageLineColor = averageLineColor ??
|
||||
AppColors.contentColorGreen.withValues(alpha: 0.8),
|
||||
tooltipBgColor = tooltipBgColor ?? AppColors.contentColorGreen,
|
||||
tooltipTextColor = tooltipTextColor ?? Colors.black;
|
||||
|
||||
final Color lineColor;
|
||||
final Color indicatorLineColor;
|
||||
final Color indicatorTouchedLineColor;
|
||||
final Color indicatorSpotStrokeColor;
|
||||
final Color indicatorTouchedSpotStrokeColor;
|
||||
final Color bottomTextColor;
|
||||
final Color bottomTouchedTextColor;
|
||||
final Color averageLineColor;
|
||||
final Color tooltipBgColor;
|
||||
final Color tooltipTextColor;
|
||||
|
||||
List<String> get weekDays =>
|
||||
const ['Sat', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri'];
|
||||
|
||||
List<double> get yValues => const [1.3, 1, 1.8, 1.5, 2.2, 1.8, 3];
|
||||
|
||||
@override
|
||||
State createState() => _LineChartSample3State();
|
||||
}
|
||||
|
||||
class _LineChartSample3State extends State<LineChartSample3> {
|
||||
late double touchedValue;
|
||||
|
||||
bool fitInsideBottomTitle = true;
|
||||
bool fitInsideLeftTitle = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
touchedValue = -1;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta) {
|
||||
if (value % 1 != 0) {
|
||||
return Container();
|
||||
}
|
||||
final style = TextStyle(
|
||||
color: AppColors.mainTextColor1.withValues(alpha: 0.5),
|
||||
fontSize: 10,
|
||||
);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = '';
|
||||
break;
|
||||
case 1:
|
||||
text = '1k calories';
|
||||
break;
|
||||
case 2:
|
||||
text = '2k calories';
|
||||
break;
|
||||
case 3:
|
||||
text = '3k calories';
|
||||
break;
|
||||
default:
|
||||
return Container();
|
||||
}
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 6,
|
||||
fitInside: fitInsideLeftTitle
|
||||
? SideTitleFitInsideData.fromTitleMeta(meta)
|
||||
: SideTitleFitInsideData.disable(),
|
||||
child: Text(text, style: style, textAlign: TextAlign.center),
|
||||
);
|
||||
}
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta) {
|
||||
final isTouched = value == touchedValue;
|
||||
final style = TextStyle(
|
||||
color: isTouched ? widget.bottomTouchedTextColor : widget.bottomTextColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
|
||||
if (value % 1 != 0) {
|
||||
return Container();
|
||||
}
|
||||
return SideTitleWidget(
|
||||
space: 4,
|
||||
meta: meta,
|
||||
fitInside: fitInsideBottomTitle
|
||||
? SideTitleFitInsideData.fromTitleMeta(meta, distanceFromEdge: 0)
|
||||
: SideTitleFitInsideData.disable(),
|
||||
child: Text(
|
||||
widget.weekDays[value.toInt()],
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Average Line',
|
||||
style: TextStyle(
|
||||
color: widget.averageLineColor.withValues(alpha: 1),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
' and ',
|
||||
style: TextStyle(
|
||||
color: AppColors.mainTextColor1,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Indicators',
|
||||
style: TextStyle(
|
||||
color: widget.indicatorLineColor.withValues(alpha: 1),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 18,
|
||||
),
|
||||
AspectRatio(
|
||||
aspectRatio: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 20.0, left: 12),
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(
|
||||
getTouchedSpotIndicator:
|
||||
(LineChartBarData barData, List<int> spotIndexes) {
|
||||
return spotIndexes.map((spotIndex) {
|
||||
final spot = barData.spots[spotIndex];
|
||||
if (spot.x == 0 || spot.x == 6) {
|
||||
return null;
|
||||
}
|
||||
return TouchedSpotIndicatorData(
|
||||
FlLine(
|
||||
color: widget.indicatorTouchedLineColor,
|
||||
strokeWidth: 4,
|
||||
),
|
||||
FlDotData(
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
if (index.isEven) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 8,
|
||||
color: Colors.white,
|
||||
strokeWidth: 5,
|
||||
strokeColor:
|
||||
widget.indicatorTouchedSpotStrokeColor,
|
||||
);
|
||||
} else {
|
||||
return FlDotSquarePainter(
|
||||
size: 16,
|
||||
color: Colors.white,
|
||||
strokeWidth: 5,
|
||||
strokeColor:
|
||||
widget.indicatorTouchedSpotStrokeColor,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
getTooltipColor: (touchedSpot) => widget.tooltipBgColor,
|
||||
getTooltipItems: (List<LineBarSpot> touchedBarSpots) {
|
||||
return touchedBarSpots.map((barSpot) {
|
||||
final flSpot = barSpot;
|
||||
if (flSpot.x == 0 || flSpot.x == 6) {
|
||||
return null;
|
||||
}
|
||||
|
||||
TextAlign textAlign;
|
||||
switch (flSpot.x.toInt()) {
|
||||
case 1:
|
||||
textAlign = TextAlign.left;
|
||||
break;
|
||||
case 5:
|
||||
textAlign = TextAlign.right;
|
||||
break;
|
||||
default:
|
||||
textAlign = TextAlign.center;
|
||||
}
|
||||
|
||||
return LineTooltipItem(
|
||||
'${widget.weekDays[flSpot.x.toInt()]} \n',
|
||||
TextStyle(
|
||||
color: widget.tooltipTextColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: flSpot.y.toString(),
|
||||
style: TextStyle(
|
||||
color: widget.tooltipTextColor,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: ' k ',
|
||||
style: TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
const TextSpan(
|
||||
text: 'calories',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
],
|
||||
textAlign: textAlign,
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
touchCallback:
|
||||
(FlTouchEvent event, LineTouchResponse? lineTouch) {
|
||||
if (!event.isInterestedForInteractions ||
|
||||
lineTouch == null ||
|
||||
lineTouch.lineBarSpots == null) {
|
||||
setState(() {
|
||||
touchedValue = -1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
final value = lineTouch.lineBarSpots![0].x;
|
||||
|
||||
if (value == 0 || value == 6) {
|
||||
setState(() {
|
||||
touchedValue = -1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
touchedValue = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
extraLinesData: ExtraLinesData(
|
||||
horizontalLines: [
|
||||
HorizontalLine(
|
||||
y: 1.8,
|
||||
color: widget.averageLineColor,
|
||||
strokeWidth: 3,
|
||||
dashArray: [20, 10],
|
||||
),
|
||||
],
|
||||
),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
isStepLineChart: true,
|
||||
spots: widget.yValues.asMap().entries.map((e) {
|
||||
return FlSpot(e.key.toDouble(), e.value);
|
||||
}).toList(),
|
||||
isCurved: false,
|
||||
barWidth: 4,
|
||||
color: widget.lineColor,
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
widget.lineColor.withValues(alpha: 0.5),
|
||||
widget.lineColor.withValues(alpha: 0),
|
||||
],
|
||||
stops: const [0.5, 1.0],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
),
|
||||
spotsLine: BarAreaSpotsLine(
|
||||
show: true,
|
||||
flLineStyle: FlLine(
|
||||
color: widget.indicatorLineColor,
|
||||
strokeWidth: 2,
|
||||
),
|
||||
checkToShowSpotLine: (spot) {
|
||||
if (spot.x == 0 || spot.x == 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
),
|
||||
),
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
if (index.isEven) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 6,
|
||||
color: Colors.white,
|
||||
strokeWidth: 3,
|
||||
strokeColor: widget.indicatorSpotStrokeColor,
|
||||
);
|
||||
} else {
|
||||
return FlDotSquarePainter(
|
||||
size: 12,
|
||||
color: Colors.white,
|
||||
strokeWidth: 3,
|
||||
strokeColor: widget.indicatorSpotStrokeColor,
|
||||
);
|
||||
}
|
||||
},
|
||||
checkToShowDot: (spot, barData) {
|
||||
return spot.x != 0 && spot.x != 6;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
minY: 0,
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: Border.all(
|
||||
color: AppColors.borderColor,
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawHorizontalLine: true,
|
||||
drawVerticalLine: true,
|
||||
checkToShowHorizontalLine: (value) => value % 1 == 0,
|
||||
checkToShowVerticalLine: (value) => value % 1 == 0,
|
||||
getDrawingHorizontalLine: (value) {
|
||||
if (value == 0) {
|
||||
return const FlLine(
|
||||
color: AppColors.contentColorOrange,
|
||||
strokeWidth: 2,
|
||||
);
|
||||
} else {
|
||||
return const FlLine(
|
||||
color: AppColors.mainGridLineColor,
|
||||
strokeWidth: 0.5,
|
||||
);
|
||||
}
|
||||
},
|
||||
getDrawingVerticalLine: (value) {
|
||||
if (value == 0) {
|
||||
return const FlLine(
|
||||
color: Colors.redAccent,
|
||||
strokeWidth: 10,
|
||||
);
|
||||
} else {
|
||||
return const FlLine(
|
||||
color: AppColors.mainGridLineColor,
|
||||
strokeWidth: 0.5,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 46,
|
||||
getTitlesWidget: leftTitleWidgets,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 40,
|
||||
getTitlesWidget: bottomTitleWidgets,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
const Text('Fit Inside Title Option'),
|
||||
const Divider(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('Left Title'),
|
||||
Switch(
|
||||
value: fitInsideLeftTitle,
|
||||
onChanged: (value) => setState(() {
|
||||
fitInsideLeftTitle = value;
|
||||
}),
|
||||
),
|
||||
const Text('Bottom Title'),
|
||||
Switch(
|
||||
value: fitInsideBottomTitle,
|
||||
onChanged: (value) => setState(() {
|
||||
fitInsideBottomTitle = value;
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
202
example/lib/presentation/samples/line/line_chart_sample4.dart
Normal file
202
example/lib/presentation/samples/line/line_chart_sample4.dart
Normal file
@@ -0,0 +1,202 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LineChartSample4 extends StatelessWidget {
|
||||
LineChartSample4({
|
||||
super.key,
|
||||
Color? mainLineColor,
|
||||
Color? belowLineColor,
|
||||
Color? aboveLineColor,
|
||||
}) : mainLineColor =
|
||||
mainLineColor ?? AppColors.contentColorYellow.withValues(alpha: 1),
|
||||
belowLineColor =
|
||||
belowLineColor ?? AppColors.contentColorPink.withValues(alpha: 1),
|
||||
aboveLineColor = aboveLineColor ??
|
||||
AppColors.contentColorPurple.withValues(alpha: 0.7);
|
||||
|
||||
final Color mainLineColor;
|
||||
final Color belowLineColor;
|
||||
final Color aboveLineColor;
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta) {
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = 'Jan';
|
||||
break;
|
||||
case 1:
|
||||
text = 'Feb';
|
||||
break;
|
||||
case 2:
|
||||
text = 'Mar';
|
||||
break;
|
||||
case 3:
|
||||
text = 'Apr';
|
||||
break;
|
||||
case 4:
|
||||
text = 'May';
|
||||
break;
|
||||
case 5:
|
||||
text = 'Jun';
|
||||
break;
|
||||
case 6:
|
||||
text = 'Jul';
|
||||
break;
|
||||
case 7:
|
||||
text = 'Aug';
|
||||
break;
|
||||
case 8:
|
||||
text = 'Sep';
|
||||
break;
|
||||
case 9:
|
||||
text = 'Oct';
|
||||
break;
|
||||
case 10:
|
||||
text = 'Nov';
|
||||
break;
|
||||
case 11:
|
||||
text = 'Dec';
|
||||
break;
|
||||
default:
|
||||
return Container();
|
||||
}
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 4,
|
||||
child: Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: mainLineColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
color: AppColors.mainTextColor3,
|
||||
fontSize: 12,
|
||||
);
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text('\$ ${value + 0.5}', style: style),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const cutOffYValue = 5.0;
|
||||
|
||||
return AspectRatio(
|
||||
aspectRatio: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 28,
|
||||
top: 22,
|
||||
bottom: 12,
|
||||
),
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: const LineTouchData(enabled: false),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: const [
|
||||
FlSpot(0, 4),
|
||||
FlSpot(1, 3.5),
|
||||
FlSpot(2, 4.5),
|
||||
FlSpot(3, 1),
|
||||
FlSpot(4, 4),
|
||||
FlSpot(5, 6),
|
||||
FlSpot(6, 6.5),
|
||||
FlSpot(7, 6),
|
||||
FlSpot(8, 4),
|
||||
FlSpot(9, 6),
|
||||
FlSpot(10, 6),
|
||||
FlSpot(11, 7),
|
||||
],
|
||||
isCurved: true,
|
||||
barWidth: 8,
|
||||
color: mainLineColor,
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
color: belowLineColor,
|
||||
cutOffY: cutOffYValue,
|
||||
applyCutOffY: true,
|
||||
),
|
||||
aboveBarData: BarAreaData(
|
||||
show: true,
|
||||
color: aboveLineColor,
|
||||
cutOffY: cutOffYValue,
|
||||
applyCutOffY: true,
|
||||
),
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
minY: 0,
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
axisNameWidget: Text(
|
||||
'2019',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: mainLineColor,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 18,
|
||||
interval: 1,
|
||||
getTitlesWidget: bottomTitleWidgets,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
axisNameSize: 20,
|
||||
axisNameWidget: const Text(
|
||||
'Value',
|
||||
style: TextStyle(
|
||||
color: AppColors.mainTextColor2,
|
||||
),
|
||||
),
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
interval: 1,
|
||||
reservedSize: 40,
|
||||
getTitlesWidget: leftTitleWidgets,
|
||||
),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: Border.all(
|
||||
color: AppColors.borderColor,
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: 1,
|
||||
checkToShowHorizontalLine: (double value) {
|
||||
return value == 1 || value == 6 || value == 4 || value == 5;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
289
example/lib/presentation/samples/line/line_chart_sample5.dart
Normal file
289
example/lib/presentation/samples/line/line_chart_sample5.dart
Normal file
@@ -0,0 +1,289 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LineChartSample5 extends StatefulWidget {
|
||||
const LineChartSample5({
|
||||
super.key,
|
||||
Color? gradientColor1,
|
||||
Color? gradientColor2,
|
||||
Color? gradientColor3,
|
||||
Color? indicatorStrokeColor,
|
||||
}) : gradientColor1 = gradientColor1 ?? AppColors.contentColorBlue,
|
||||
gradientColor2 = gradientColor2 ?? AppColors.contentColorPink,
|
||||
gradientColor3 = gradientColor3 ?? AppColors.contentColorRed,
|
||||
indicatorStrokeColor = indicatorStrokeColor ?? AppColors.mainTextColor1;
|
||||
|
||||
final Color gradientColor1;
|
||||
final Color gradientColor2;
|
||||
final Color gradientColor3;
|
||||
final Color indicatorStrokeColor;
|
||||
|
||||
@override
|
||||
State<LineChartSample5> createState() => _LineChartSample5State();
|
||||
}
|
||||
|
||||
class _LineChartSample5State extends State<LineChartSample5> {
|
||||
List<int> showingTooltipOnSpots = [1, 3, 5];
|
||||
|
||||
List<FlSpot> get allSpots => const [
|
||||
FlSpot(0, 1),
|
||||
FlSpot(1, 2),
|
||||
FlSpot(2, 1.5),
|
||||
FlSpot(3, 3),
|
||||
FlSpot(4, 3.5),
|
||||
FlSpot(5, 5),
|
||||
FlSpot(6, 8),
|
||||
];
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth) {
|
||||
final style = TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.contentColorPink,
|
||||
fontFamily: 'Digital',
|
||||
fontSize: 18 * chartWidth / 500,
|
||||
);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = '00:00';
|
||||
break;
|
||||
case 1:
|
||||
text = '04:00';
|
||||
break;
|
||||
case 2:
|
||||
text = '08:00';
|
||||
break;
|
||||
case 3:
|
||||
text = '12:00';
|
||||
break;
|
||||
case 4:
|
||||
text = '16:00';
|
||||
break;
|
||||
case 5:
|
||||
text = '20:00';
|
||||
break;
|
||||
case 6:
|
||||
text = '23:59';
|
||||
break;
|
||||
default:
|
||||
return Container();
|
||||
}
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(text, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final lineBarsData = [
|
||||
LineChartBarData(
|
||||
showingIndicators: showingTooltipOnSpots,
|
||||
spots: allSpots,
|
||||
isCurved: true,
|
||||
barWidth: 4,
|
||||
shadow: const Shadow(
|
||||
blurRadius: 8,
|
||||
),
|
||||
belowBarData: BarAreaData(
|
||||
show: true,
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
widget.gradientColor1.withValues(alpha: 0.4),
|
||||
widget.gradientColor2.withValues(alpha: 0.4),
|
||||
widget.gradientColor3.withValues(alpha: 0.4),
|
||||
],
|
||||
),
|
||||
),
|
||||
dotData: const FlDotData(show: false),
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
widget.gradientColor1,
|
||||
widget.gradientColor2,
|
||||
widget.gradientColor3,
|
||||
],
|
||||
stops: const [0.1, 0.4, 0.9],
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
final tooltipsOnBar = lineBarsData[0];
|
||||
|
||||
return AspectRatio(
|
||||
aspectRatio: 2.5,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 24.0,
|
||||
vertical: 10,
|
||||
),
|
||||
child: LayoutBuilder(builder: (context, constraints) {
|
||||
return LineChart(
|
||||
LineChartData(
|
||||
showingTooltipIndicators: showingTooltipOnSpots.map((index) {
|
||||
return ShowingTooltipIndicators([
|
||||
LineBarSpot(
|
||||
tooltipsOnBar,
|
||||
lineBarsData.indexOf(tooltipsOnBar),
|
||||
tooltipsOnBar.spots[index],
|
||||
),
|
||||
]);
|
||||
}).toList(),
|
||||
lineTouchData: LineTouchData(
|
||||
enabled: true,
|
||||
handleBuiltInTouches: false,
|
||||
touchCallback:
|
||||
(FlTouchEvent event, LineTouchResponse? response) {
|
||||
if (response == null || response.lineBarSpots == null) {
|
||||
return;
|
||||
}
|
||||
if (event is FlTapUpEvent) {
|
||||
final spotIndex = response.lineBarSpots!.first.spotIndex;
|
||||
setState(() {
|
||||
if (showingTooltipOnSpots.contains(spotIndex)) {
|
||||
showingTooltipOnSpots.remove(spotIndex);
|
||||
} else {
|
||||
showingTooltipOnSpots.add(spotIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mouseCursorResolver:
|
||||
(FlTouchEvent event, LineTouchResponse? response) {
|
||||
if (response == null || response.lineBarSpots == null) {
|
||||
return SystemMouseCursors.basic;
|
||||
}
|
||||
return SystemMouseCursors.click;
|
||||
},
|
||||
getTouchedSpotIndicator:
|
||||
(LineChartBarData barData, List<int> spotIndexes) {
|
||||
return spotIndexes.map((index) {
|
||||
return TouchedSpotIndicatorData(
|
||||
const FlLine(
|
||||
color: Colors.pink,
|
||||
),
|
||||
FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) =>
|
||||
FlDotCirclePainter(
|
||||
radius: 8,
|
||||
color: lerpGradient(
|
||||
barData.gradient!.colors,
|
||||
barData.gradient!.stops!,
|
||||
percent / 100,
|
||||
),
|
||||
strokeWidth: 2,
|
||||
strokeColor: widget.indicatorStrokeColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
getTooltipColor: (touchedSpot) => Colors.pink,
|
||||
tooltipBorderRadius: BorderRadius.circular(8),
|
||||
getTooltipItems: (List<LineBarSpot> lineBarsSpot) {
|
||||
return lineBarsSpot.map((lineBarSpot) {
|
||||
return LineTooltipItem(
|
||||
lineBarSpot.y.toString(),
|
||||
const TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
),
|
||||
lineBarsData: lineBarsData,
|
||||
minY: 0,
|
||||
titlesData: FlTitlesData(
|
||||
leftTitles: const AxisTitles(
|
||||
axisNameWidget: Text('count'),
|
||||
axisNameSize: 24,
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
reservedSize: 0,
|
||||
),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
interval: 1,
|
||||
getTitlesWidget: (value, meta) {
|
||||
return bottomTitleWidgets(
|
||||
value,
|
||||
meta,
|
||||
constraints.maxWidth,
|
||||
);
|
||||
},
|
||||
reservedSize: 30,
|
||||
),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
axisNameWidget: Text('count'),
|
||||
sideTitles: SideTitles(
|
||||
showTitles: false,
|
||||
reservedSize: 0,
|
||||
),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
axisNameWidget: Text(
|
||||
'Wall clock',
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
axisNameSize: 24,
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 0,
|
||||
),
|
||||
),
|
||||
),
|
||||
gridData: const FlGridData(show: false),
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: Border.all(
|
||||
color: AppColors.borderColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Lerps between a [LinearGradient] colors, based on [t]
|
||||
Color lerpGradient(List<Color> colors, List<double> stops, double t) {
|
||||
if (colors.isEmpty) {
|
||||
throw ArgumentError('"colors" is empty.');
|
||||
} else if (colors.length == 1) {
|
||||
return colors[0];
|
||||
}
|
||||
|
||||
if (stops.length != colors.length) {
|
||||
stops = [];
|
||||
|
||||
/// provided gradientColorStops is invalid and we calculate it here
|
||||
colors.asMap().forEach((index, color) {
|
||||
final percent = 1.0 / (colors.length - 1);
|
||||
stops.add(percent * index);
|
||||
});
|
||||
}
|
||||
|
||||
for (var s = 0; s < stops.length - 1; s++) {
|
||||
final leftStop = stops[s];
|
||||
final rightStop = stops[s + 1];
|
||||
final leftColor = colors[s];
|
||||
final rightColor = colors[s + 1];
|
||||
if (t <= leftStop) {
|
||||
return leftColor;
|
||||
} else if (t < rightStop) {
|
||||
final sectionT = (t - leftStop) / (rightStop - leftStop);
|
||||
return Color.lerp(leftColor, rightColor, sectionT)!;
|
||||
}
|
||||
}
|
||||
return colors.last;
|
||||
}
|
||||
286
example/lib/presentation/samples/line/line_chart_sample6.dart
Normal file
286
example/lib/presentation/samples/line/line_chart_sample6.dart
Normal file
@@ -0,0 +1,286 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/util/extensions/color_extensions.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class LineChartSample6 extends StatelessWidget {
|
||||
LineChartSample6({
|
||||
super.key,
|
||||
Color? line1Color1,
|
||||
Color? line1Color2,
|
||||
Color? line2Color1,
|
||||
Color? line2Color2,
|
||||
}) : line1Color1 = line1Color1 ?? AppColors.contentColorOrange,
|
||||
line1Color2 = line1Color2 ?? AppColors.contentColorOrange.darken(60),
|
||||
line2Color1 = line2Color1 ?? AppColors.contentColorBlue.darken(60),
|
||||
line2Color2 = line2Color2 ?? AppColors.contentColorBlue {
|
||||
minSpotX = spots.first.x;
|
||||
maxSpotX = spots.first.x;
|
||||
minSpotY = spots.first.y;
|
||||
maxSpotY = spots.first.y;
|
||||
|
||||
for (final spot in spots) {
|
||||
if (spot.x > maxSpotX) {
|
||||
maxSpotX = spot.x;
|
||||
}
|
||||
|
||||
if (spot.x < minSpotX) {
|
||||
minSpotX = spot.x;
|
||||
}
|
||||
|
||||
if (spot.y > maxSpotY) {
|
||||
maxSpotY = spot.y;
|
||||
}
|
||||
|
||||
if (spot.y < minSpotY) {
|
||||
minSpotY = spot.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Color line1Color1;
|
||||
final Color line1Color2;
|
||||
final Color line2Color1;
|
||||
final Color line2Color2;
|
||||
|
||||
final spots = const [
|
||||
FlSpot(0, 1),
|
||||
FlSpot(2, 5),
|
||||
FlSpot(4, 3),
|
||||
FlSpot(6, 5),
|
||||
];
|
||||
|
||||
final spots2 = const [
|
||||
FlSpot(0, 3),
|
||||
FlSpot(2, 1),
|
||||
FlSpot(4, 2),
|
||||
FlSpot(6, 1),
|
||||
];
|
||||
|
||||
late double minSpotX;
|
||||
late double maxSpotX;
|
||||
late double minSpotY;
|
||||
late double maxSpotY;
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta) {
|
||||
final style = TextStyle(
|
||||
color: line1Color1,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
);
|
||||
|
||||
final intValue = reverseY(value, minSpotY, maxSpotY).toInt();
|
||||
|
||||
if (intValue == (maxSpotY + minSpotY)) {
|
||||
return Text('', style: style);
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: Text(
|
||||
intValue.toString(),
|
||||
style: style,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget rightTitleWidgets(double value, TitleMeta meta) {
|
||||
final style = TextStyle(
|
||||
color: line2Color2,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 18,
|
||||
);
|
||||
final intValue = reverseY(value, minSpotY, maxSpotY).toInt();
|
||||
|
||||
if (intValue == (maxSpotY + minSpotY)) {
|
||||
return Text('', style: style);
|
||||
}
|
||||
|
||||
return Text(intValue.toString(), style: style, textAlign: TextAlign.right);
|
||||
}
|
||||
|
||||
Widget topTitleWidgets(double value, TitleMeta meta) {
|
||||
if (value % 1 != 0) {
|
||||
return Container();
|
||||
}
|
||||
const style = TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.mainTextColor2,
|
||||
);
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(value.toInt().toString(), style: style),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 22, bottom: 20),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 2,
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
tooltipBorderRadius: BorderRadius.zero,
|
||||
getTooltipColor: (spot) => Colors.white,
|
||||
getTooltipItems: (List<LineBarSpot> touchedSpots) {
|
||||
return touchedSpots.map((LineBarSpot touchedSpot) {
|
||||
return LineTooltipItem(
|
||||
touchedSpot.y.toString(),
|
||||
TextStyle(
|
||||
color: touchedSpot.bar.gradient!.colors.first,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 20,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
getTouchedSpotIndicator: (
|
||||
_,
|
||||
indicators,
|
||||
) {
|
||||
return indicators
|
||||
.map((int index) => const TouchedSpotIndicatorData(
|
||||
FlLine(color: Colors.transparent),
|
||||
FlDotData(show: false),
|
||||
))
|
||||
.toList();
|
||||
},
|
||||
touchSpotThreshold: 12,
|
||||
distanceCalculator:
|
||||
(Offset touchPoint, Offset spotPixelCoordinates) =>
|
||||
(touchPoint - spotPixelCoordinates).distance,
|
||||
),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
line1Color1,
|
||||
line1Color2,
|
||||
],
|
||||
),
|
||||
spots: reverseSpots(spots, minSpotY, maxSpotY),
|
||||
isCurved: true,
|
||||
isStrokeCapRound: true,
|
||||
barWidth: 10,
|
||||
belowBarData: BarAreaData(
|
||||
show: false,
|
||||
),
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 12,
|
||||
color: Color.lerp(
|
||||
line1Color1,
|
||||
line1Color2,
|
||||
percent / 100,
|
||||
)!,
|
||||
strokeColor: Colors.white,
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
LineChartBarData(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
line2Color1,
|
||||
line2Color2,
|
||||
],
|
||||
),
|
||||
spots: reverseSpots(spots2, minSpotY, maxSpotY),
|
||||
isCurved: true,
|
||||
isStrokeCapRound: true,
|
||||
barWidth: 10,
|
||||
belowBarData: BarAreaData(
|
||||
show: false,
|
||||
),
|
||||
dotData: FlDotData(
|
||||
show: true,
|
||||
getDotPainter: (spot, percent, barData, index) {
|
||||
return FlDotCirclePainter(
|
||||
radius: 12,
|
||||
color: Color.lerp(
|
||||
line2Color1,
|
||||
line2Color2,
|
||||
percent / 100,
|
||||
)!,
|
||||
strokeColor: Colors.white,
|
||||
strokeWidth: 1,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
minY: 0,
|
||||
maxY: maxSpotY + minSpotY,
|
||||
titlesData: FlTitlesData(
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: leftTitleWidgets,
|
||||
reservedSize: 38,
|
||||
),
|
||||
),
|
||||
rightTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: rightTitleWidgets,
|
||||
reservedSize: 30,
|
||||
),
|
||||
),
|
||||
bottomTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 32,
|
||||
getTitlesWidget: topTitleWidgets,
|
||||
),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: true,
|
||||
checkToShowHorizontalLine: (value) {
|
||||
final intValue = reverseY(value, minSpotY, maxSpotY).toInt();
|
||||
|
||||
if (intValue == (maxSpotY + minSpotY).toInt()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: const Border(
|
||||
left: BorderSide(color: AppColors.borderColor),
|
||||
top: BorderSide(color: AppColors.borderColor),
|
||||
bottom: BorderSide(color: Colors.transparent),
|
||||
right: BorderSide(color: Colors.transparent),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
double reverseY(double y, double minX, double maxX) {
|
||||
return (maxX + minX) - y;
|
||||
}
|
||||
|
||||
List<FlSpot> reverseSpots(List<FlSpot> inputSpots, double minY, double maxY) {
|
||||
return inputSpots.map((spot) {
|
||||
return spot.copyWith(y: (maxY + minY) - spot.y);
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
193
example/lib/presentation/samples/line/line_chart_sample7.dart
Normal file
193
example/lib/presentation/samples/line/line_chart_sample7.dart
Normal file
@@ -0,0 +1,193 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LineChartSample7 extends StatelessWidget {
|
||||
LineChartSample7({
|
||||
super.key,
|
||||
Color? line1Color,
|
||||
Color? line2Color,
|
||||
Color? betweenColor,
|
||||
}) : line1Color = line1Color ?? AppColors.contentColorGreen,
|
||||
line2Color = line2Color ?? AppColors.contentColorRed,
|
||||
betweenColor =
|
||||
betweenColor ?? AppColors.contentColorRed.withValues(alpha: 0.5);
|
||||
|
||||
final Color line1Color;
|
||||
final Color line2Color;
|
||||
final Color betweenColor;
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
);
|
||||
String text;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
text = 'Jan';
|
||||
break;
|
||||
case 1:
|
||||
text = 'Feb';
|
||||
break;
|
||||
case 2:
|
||||
text = 'Mar';
|
||||
break;
|
||||
case 3:
|
||||
text = 'Apr';
|
||||
break;
|
||||
case 4:
|
||||
text = 'May';
|
||||
break;
|
||||
case 5:
|
||||
text = 'Jun';
|
||||
break;
|
||||
case 6:
|
||||
text = 'Jul';
|
||||
break;
|
||||
case 7:
|
||||
text = 'Aug';
|
||||
break;
|
||||
case 8:
|
||||
text = 'Sep';
|
||||
break;
|
||||
case 9:
|
||||
text = 'Oct';
|
||||
break;
|
||||
case 10:
|
||||
text = 'Nov';
|
||||
break;
|
||||
case 11:
|
||||
text = 'Dec';
|
||||
break;
|
||||
default:
|
||||
return Container();
|
||||
}
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 4,
|
||||
child: Text(text, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta) {
|
||||
const style = TextStyle(fontSize: 10);
|
||||
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(
|
||||
'\$ ${value + 0.5}',
|
||||
style: style,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10,
|
||||
right: 18,
|
||||
top: 10,
|
||||
bottom: 4,
|
||||
),
|
||||
child: LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: const LineTouchData(enabled: false),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: const [
|
||||
FlSpot(0, 4),
|
||||
FlSpot(1, 3.5),
|
||||
FlSpot(2, 4.5),
|
||||
FlSpot(3, 1),
|
||||
FlSpot(4, 4),
|
||||
FlSpot(5, 6),
|
||||
FlSpot(6, 6.5),
|
||||
FlSpot(7, 6),
|
||||
FlSpot(8, 4),
|
||||
FlSpot(9, 6),
|
||||
FlSpot(10, 6),
|
||||
FlSpot(11, 7),
|
||||
],
|
||||
isCurved: true,
|
||||
barWidth: 2,
|
||||
color: line1Color,
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
),
|
||||
),
|
||||
LineChartBarData(
|
||||
spots: const [
|
||||
FlSpot(0, 7),
|
||||
FlSpot(1, 3),
|
||||
FlSpot(2, 4),
|
||||
FlSpot(3, 2),
|
||||
FlSpot(4, 3),
|
||||
FlSpot(5, 4),
|
||||
FlSpot(6, 5),
|
||||
FlSpot(7, 3),
|
||||
FlSpot(8, 1),
|
||||
FlSpot(9, 8),
|
||||
FlSpot(10, 1),
|
||||
FlSpot(11, 3),
|
||||
],
|
||||
isCurved: false,
|
||||
barWidth: 2,
|
||||
color: line2Color,
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
betweenBarsData: [
|
||||
BetweenBarsData(
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
color: betweenColor,
|
||||
)
|
||||
],
|
||||
minY: 0,
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
interval: 1,
|
||||
getTitlesWidget: bottomTitleWidgets,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: leftTitleWidgets,
|
||||
interval: 1,
|
||||
reservedSize: 36,
|
||||
),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
horizontalInterval: 1,
|
||||
checkToShowHorizontalLine: (double value) {
|
||||
return value == 1 || value == 6 || value == 4 || value == 5;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
291
example/lib/presentation/samples/line/line_chart_sample8.dart
Normal file
291
example/lib/presentation/samples/line/line_chart_sample8.dart
Normal file
@@ -0,0 +1,291 @@
|
||||
import 'dart:async';
|
||||
import 'dart:ui' as ui;
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class LineChartSample8 extends StatefulWidget {
|
||||
const LineChartSample8({super.key});
|
||||
|
||||
@override
|
||||
State<LineChartSample8> createState() => _LineChartSample8State();
|
||||
}
|
||||
|
||||
class _LineChartSample8State extends State<LineChartSample8> {
|
||||
List<Color> gradientColors = const [
|
||||
Color(0xffEEF3FE),
|
||||
Color(0xffEEF3FE),
|
||||
];
|
||||
|
||||
bool showAvg = false;
|
||||
|
||||
Future<ui.Image> loadImage(String asset) async {
|
||||
final data = await rootBundle.load(asset);
|
||||
final codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
|
||||
final fi = await codec.getNextFrame();
|
||||
return fi.image;
|
||||
}
|
||||
|
||||
Future<SizedPicture> loadSvg() async {
|
||||
const rawSvg =
|
||||
'<svg height="14" width="14" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd" transform="translate(-.000014)"><circle cx="7" cy="7" fill="#495DFF" r="7"/><path d="m7 10.9999976c1.6562389 0 2.99998569-1.34374678 2.99998569-2.99999283s-1.34374679-4.99998808-2.99998569-4.99998808c-1.6562532 0-3 3.34374203-3 4.99998808s1.3437468 2.99999283 3 2.99999283z" fill="#fff" fill-rule="nonzero"/></g></svg>';
|
||||
|
||||
final pictureInfo =
|
||||
await vg.loadPicture(const SvgStringLoader(rawSvg), null);
|
||||
|
||||
final sizedPicture = SizedPicture(pictureInfo.picture, 14, 14);
|
||||
return sizedPicture;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<SizedPicture>(
|
||||
future: loadSvg(),
|
||||
builder: (BuildContext context, imageSnapshot) {
|
||||
if (imageSnapshot.connectionState == ConnectionState.done) {
|
||||
return Stack(
|
||||
children: <Widget>[
|
||||
AspectRatio(
|
||||
aspectRatio: 1.70,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 18,
|
||||
left: 12,
|
||||
top: 24,
|
||||
bottom: 12,
|
||||
),
|
||||
child: LineChart(
|
||||
mainData(imageSnapshot.data!),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta) {
|
||||
const style = TextStyle(
|
||||
fontSize: 10,
|
||||
color: AppColors.mainTextColor1,
|
||||
);
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Text(meta.formattedValue, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta) {
|
||||
IconData icon;
|
||||
Color color;
|
||||
switch (value.toInt()) {
|
||||
case 0:
|
||||
icon = Icons.wb_sunny;
|
||||
color = AppColors.contentColorYellow;
|
||||
break;
|
||||
case 2:
|
||||
icon = Icons.wine_bar_sharp;
|
||||
color = AppColors.contentColorRed;
|
||||
break;
|
||||
case 4:
|
||||
icon = Icons.watch_later;
|
||||
color = AppColors.contentColorGreen;
|
||||
break;
|
||||
case 6:
|
||||
icon = Icons.whatshot;
|
||||
color = AppColors.contentColorOrange;
|
||||
break;
|
||||
default:
|
||||
throw StateError('Invalid');
|
||||
}
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
child: Icon(
|
||||
icon,
|
||||
color: color,
|
||||
size: 32,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
LineChartData mainData(SizedPicture sizedPicture) {
|
||||
return LineChartData(
|
||||
rangeAnnotations: RangeAnnotations(
|
||||
verticalRangeAnnotations: [
|
||||
VerticalRangeAnnotation(
|
||||
x1: 2,
|
||||
x2: 5,
|
||||
color: AppColors.contentColorBlue.withValues(alpha: 0.2),
|
||||
),
|
||||
VerticalRangeAnnotation(
|
||||
x1: 8,
|
||||
x2: 9,
|
||||
color: AppColors.contentColorBlue.withValues(alpha: 0.2),
|
||||
),
|
||||
],
|
||||
horizontalRangeAnnotations: [
|
||||
HorizontalRangeAnnotation(
|
||||
y1: 2,
|
||||
y2: 3,
|
||||
color: AppColors.contentColorGreen.withValues(alpha: 0.3),
|
||||
),
|
||||
],
|
||||
),
|
||||
// uncomment to see ExtraLines with RangeAnnotations
|
||||
extraLinesData: ExtraLinesData(
|
||||
// extraLinesOnTop: true,
|
||||
horizontalLines: [
|
||||
HorizontalLine(
|
||||
y: 5,
|
||||
color: AppColors.contentColorGreen,
|
||||
strokeWidth: 2,
|
||||
dashArray: [5, 10],
|
||||
label: HorizontalLineLabel(
|
||||
show: true,
|
||||
alignment: Alignment.topRight,
|
||||
padding: const EdgeInsets.only(right: 5, bottom: 5),
|
||||
style: const TextStyle(
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
labelResolver: (line) => 'H: ${line.y}',
|
||||
),
|
||||
),
|
||||
],
|
||||
verticalLines: [
|
||||
VerticalLine(
|
||||
x: 5.7,
|
||||
color: AppColors.contentColorBlue,
|
||||
strokeWidth: 2,
|
||||
dashArray: [5, 10],
|
||||
label: VerticalLineLabel(
|
||||
show: true,
|
||||
alignment: Alignment.bottomRight,
|
||||
padding: const EdgeInsets.only(left: 5, bottom: 5),
|
||||
style: const TextStyle(
|
||||
fontSize: 9,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
direction: LabelDirection.vertical,
|
||||
labelResolver: (line) => 'V: ${line.x}',
|
||||
),
|
||||
),
|
||||
VerticalLine(
|
||||
x: 8.5,
|
||||
color: Colors.transparent,
|
||||
sizedPicture: sizedPicture,
|
||||
),
|
||||
VerticalLine(
|
||||
x: 3.5,
|
||||
color: Colors.transparent,
|
||||
sizedPicture: sizedPicture,
|
||||
)
|
||||
],
|
||||
),
|
||||
gridData: const FlGridData(
|
||||
show: true,
|
||||
drawVerticalLine: false,
|
||||
drawHorizontalLine: false,
|
||||
verticalInterval: 1,
|
||||
),
|
||||
titlesData: FlTitlesData(
|
||||
show: true,
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
reservedSize: 30,
|
||||
getTitlesWidget: bottomTitleWidgets,
|
||||
interval: 4,
|
||||
),
|
||||
),
|
||||
leftTitles: AxisTitles(
|
||||
drawBelowEverything: true,
|
||||
sideTitles: SideTitles(
|
||||
interval: 2,
|
||||
showTitles: true,
|
||||
getTitlesWidget: leftTitleWidgets,
|
||||
reservedSize: 40,
|
||||
),
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
),
|
||||
lineTouchData: LineTouchData(
|
||||
getTouchLineEnd: (data, index) => double.infinity,
|
||||
getTouchedSpotIndicator:
|
||||
(LineChartBarData barData, List<int> spotIndexes) {
|
||||
return spotIndexes.map((spotIndex) {
|
||||
return TouchedSpotIndicatorData(
|
||||
const FlLine(color: AppColors.contentColorOrange, strokeWidth: 3),
|
||||
FlDotData(
|
||||
getDotPainter: (spot, percent, barData, index) =>
|
||||
FlDotCirclePainter(
|
||||
radius: 8,
|
||||
color: AppColors.contentColorOrange,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
getTooltipColor: (touchedSpot) => AppColors.contentColorRed,
|
||||
getTooltipItems: (List<LineBarSpot> touchedSpots) => touchedSpots
|
||||
.map((LineBarSpot touchedSpot) => LineTooltipItem(
|
||||
touchedSpot.y.toString(),
|
||||
const TextStyle(
|
||||
color: AppColors.contentColorWhite,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: true,
|
||||
border: Border.all(
|
||||
color: AppColors.borderColor,
|
||||
),
|
||||
),
|
||||
minX: 0,
|
||||
maxX: 11,
|
||||
minY: 0,
|
||||
maxY: 6,
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
spots: const [
|
||||
FlSpot(0, 1),
|
||||
FlSpot(2, 1),
|
||||
FlSpot(4.9, 5),
|
||||
FlSpot(6.8, 5),
|
||||
FlSpot(7.5, 3.5),
|
||||
FlSpot.nullSpot,
|
||||
FlSpot(7.5, 2),
|
||||
FlSpot(8, 1),
|
||||
FlSpot(10, 2),
|
||||
FlSpot(11, 2.5),
|
||||
],
|
||||
dashArray: [10, 6],
|
||||
isCurved: true,
|
||||
color: AppColors.contentColorRed,
|
||||
barWidth: 4,
|
||||
isStrokeCapRound: true,
|
||||
dotData: const FlDotData(
|
||||
show: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
154
example/lib/presentation/samples/line/line_chart_sample9.dart
Normal file
154
example/lib/presentation/samples/line/line_chart_sample9.dart
Normal file
@@ -0,0 +1,154 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class LineChartSample9 extends StatelessWidget {
|
||||
LineChartSample9({super.key});
|
||||
|
||||
final spots = List.generate(101, (i) => (i - 50) / 10)
|
||||
.map((x) => FlSpot(x, cos(x)))
|
||||
.toList();
|
||||
|
||||
Widget bottomTitleWidgets(double value, TitleMeta meta, double chartWidth) {
|
||||
if (value % 1 != 0) {
|
||||
return Container();
|
||||
}
|
||||
final style = TextStyle(
|
||||
color: AppColors.contentColorBlue,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: min(18, 18 * chartWidth / 300),
|
||||
);
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 16,
|
||||
child: Text(meta.formattedValue, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
Widget leftTitleWidgets(double value, TitleMeta meta, double chartWidth) {
|
||||
final style = TextStyle(
|
||||
color: AppColors.contentColorYellow,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: min(18, 18 * chartWidth / 300),
|
||||
);
|
||||
return SideTitleWidget(
|
||||
meta: meta,
|
||||
space: 16,
|
||||
child: Text(meta.formattedValue, style: style),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
bottom: 12,
|
||||
right: 20,
|
||||
top: 20,
|
||||
),
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return LineChart(
|
||||
LineChartData(
|
||||
lineTouchData: LineTouchData(
|
||||
touchTooltipData: LineTouchTooltipData(
|
||||
maxContentWidth: 100,
|
||||
getTooltipColor: (touchedSpot) => Colors.black,
|
||||
getTooltipItems: (touchedSpots) {
|
||||
return touchedSpots.map((LineBarSpot touchedSpot) {
|
||||
final textStyle = TextStyle(
|
||||
color: touchedSpot.bar.gradient?.colors[0] ??
|
||||
touchedSpot.bar.color,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
);
|
||||
return LineTooltipItem(
|
||||
'${touchedSpot.x}, ${touchedSpot.y.toStringAsFixed(2)}',
|
||||
textStyle,
|
||||
);
|
||||
}).toList();
|
||||
},
|
||||
),
|
||||
handleBuiltInTouches: true,
|
||||
getTouchLineStart: (data, index) => 0,
|
||||
),
|
||||
lineBarsData: [
|
||||
LineChartBarData(
|
||||
color: AppColors.contentColorPink,
|
||||
spots: spots,
|
||||
isCurved: true,
|
||||
isStrokeCapRound: true,
|
||||
barWidth: 3,
|
||||
belowBarData: BarAreaData(
|
||||
show: false,
|
||||
),
|
||||
dotData: const FlDotData(show: false),
|
||||
),
|
||||
],
|
||||
minY: -1.5,
|
||||
maxY: 1.5,
|
||||
titlesData: FlTitlesData(
|
||||
leftTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, meta) =>
|
||||
leftTitleWidgets(value, meta, constraints.maxWidth),
|
||||
reservedSize: 56,
|
||||
),
|
||||
drawBelowEverything: true,
|
||||
),
|
||||
rightTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
bottomTitles: AxisTitles(
|
||||
sideTitles: SideTitles(
|
||||
showTitles: true,
|
||||
getTitlesWidget: (value, meta) =>
|
||||
bottomTitleWidgets(value, meta, constraints.maxWidth),
|
||||
reservedSize: 36,
|
||||
interval: 1,
|
||||
),
|
||||
drawBelowEverything: true,
|
||||
),
|
||||
topTitles: const AxisTitles(
|
||||
sideTitles: SideTitles(showTitles: false),
|
||||
),
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawHorizontalLine: true,
|
||||
drawVerticalLine: true,
|
||||
horizontalInterval: 1.5,
|
||||
verticalInterval: 5,
|
||||
checkToShowHorizontalLine: (value) {
|
||||
return value.toInt() == 0;
|
||||
},
|
||||
getDrawingHorizontalLine: (_) => FlLine(
|
||||
color: AppColors.contentColorBlue.withValues(alpha: 1),
|
||||
dashArray: [8, 2],
|
||||
strokeWidth: 0.8,
|
||||
),
|
||||
getDrawingVerticalLine: (_) => FlLine(
|
||||
color: AppColors.contentColorYellow.withValues(alpha: 1),
|
||||
dashArray: [8, 2],
|
||||
strokeWidth: 0.8,
|
||||
),
|
||||
checkToShowVerticalLine: (value) {
|
||||
return value.toInt() == 0;
|
||||
},
|
||||
),
|
||||
borderData: FlBorderData(show: false),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
173
example/lib/presentation/samples/pie/pie_chart_sample1.dart
Normal file
173
example/lib/presentation/samples/pie/pie_chart_sample1.dart
Normal file
@@ -0,0 +1,173 @@
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/presentation/widgets/indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PieChartSample1 extends StatefulWidget {
|
||||
const PieChartSample1({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => PieChartSample1State();
|
||||
}
|
||||
|
||||
class PieChartSample1State extends State {
|
||||
int touchedIndex = -1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.3,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
height: 28,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
Indicator(
|
||||
color: AppColors.contentColorBlue,
|
||||
text: 'One',
|
||||
isSquare: false,
|
||||
size: touchedIndex == 0 ? 18 : 16,
|
||||
textColor: touchedIndex == 0
|
||||
? AppColors.mainTextColor1
|
||||
: AppColors.mainTextColor3,
|
||||
),
|
||||
Indicator(
|
||||
color: AppColors.contentColorYellow,
|
||||
text: 'Two',
|
||||
isSquare: false,
|
||||
size: touchedIndex == 1 ? 18 : 16,
|
||||
textColor: touchedIndex == 1
|
||||
? AppColors.mainTextColor1
|
||||
: AppColors.mainTextColor3,
|
||||
),
|
||||
Indicator(
|
||||
color: AppColors.contentColorPink,
|
||||
text: 'Three',
|
||||
isSquare: false,
|
||||
size: touchedIndex == 2 ? 18 : 16,
|
||||
textColor: touchedIndex == 2
|
||||
? AppColors.mainTextColor1
|
||||
: AppColors.mainTextColor3,
|
||||
),
|
||||
Indicator(
|
||||
color: AppColors.contentColorGreen,
|
||||
text: 'Four',
|
||||
isSquare: false,
|
||||
size: touchedIndex == 3 ? 18 : 16,
|
||||
textColor: touchedIndex == 3
|
||||
? AppColors.mainTextColor1
|
||||
: AppColors.mainTextColor3,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 18,
|
||||
),
|
||||
Expanded(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
pieTouchData: PieTouchData(
|
||||
touchCallback: (FlTouchEvent event, pieTouchResponse) {
|
||||
setState(() {
|
||||
if (!event.isInterestedForInteractions ||
|
||||
pieTouchResponse == null ||
|
||||
pieTouchResponse.touchedSection == null) {
|
||||
touchedIndex = -1;
|
||||
return;
|
||||
}
|
||||
touchedIndex = pieTouchResponse
|
||||
.touchedSection!.touchedSectionIndex;
|
||||
});
|
||||
},
|
||||
),
|
||||
startDegreeOffset: 180,
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
sectionsSpace: 1,
|
||||
centerSpaceRadius: 0,
|
||||
sections: showingSections(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<PieChartSectionData> showingSections() {
|
||||
return List.generate(
|
||||
4,
|
||||
(i) {
|
||||
final isTouched = i == touchedIndex;
|
||||
const color0 = AppColors.contentColorBlue;
|
||||
const color1 = AppColors.contentColorYellow;
|
||||
const color2 = AppColors.contentColorPink;
|
||||
const color3 = AppColors.contentColorGreen;
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
return PieChartSectionData(
|
||||
color: color0,
|
||||
value: 25,
|
||||
title: '',
|
||||
radius: 80,
|
||||
titlePositionPercentageOffset: 0.55,
|
||||
borderSide: isTouched
|
||||
? const BorderSide(
|
||||
color: AppColors.contentColorWhite, width: 6)
|
||||
: BorderSide(
|
||||
color: AppColors.contentColorWhite.withValues(alpha: 0)),
|
||||
);
|
||||
case 1:
|
||||
return PieChartSectionData(
|
||||
color: color1,
|
||||
value: 25,
|
||||
title: '',
|
||||
radius: 65,
|
||||
titlePositionPercentageOffset: 0.55,
|
||||
borderSide: isTouched
|
||||
? const BorderSide(
|
||||
color: AppColors.contentColorWhite, width: 6)
|
||||
: BorderSide(
|
||||
color: AppColors.contentColorWhite.withValues(alpha: 0)),
|
||||
);
|
||||
case 2:
|
||||
return PieChartSectionData(
|
||||
color: color2,
|
||||
value: 25,
|
||||
title: '',
|
||||
radius: 60,
|
||||
titlePositionPercentageOffset: 0.6,
|
||||
borderSide: isTouched
|
||||
? const BorderSide(
|
||||
color: AppColors.contentColorWhite, width: 6)
|
||||
: BorderSide(
|
||||
color: AppColors.contentColorWhite.withValues(alpha: 0)),
|
||||
);
|
||||
case 3:
|
||||
return PieChartSectionData(
|
||||
color: color3,
|
||||
value: 25,
|
||||
title: '',
|
||||
radius: 70,
|
||||
titlePositionPercentageOffset: 0.55,
|
||||
borderSide: isTouched
|
||||
? const BorderSide(
|
||||
color: AppColors.contentColorWhite, width: 6)
|
||||
: BorderSide(
|
||||
color: AppColors.contentColorWhite.withValues(alpha: 0)),
|
||||
);
|
||||
default:
|
||||
throw Error();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
164
example/lib/presentation/samples/pie/pie_chart_sample2.dart
Normal file
164
example/lib/presentation/samples/pie/pie_chart_sample2.dart
Normal file
@@ -0,0 +1,164 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_chart_app/presentation/widgets/indicator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PieChartSample2 extends StatefulWidget {
|
||||
const PieChartSample2({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => PieChart2State();
|
||||
}
|
||||
|
||||
class PieChart2State extends State {
|
||||
int touchedIndex = -1;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.3,
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
height: 18,
|
||||
),
|
||||
Expanded(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
pieTouchData: PieTouchData(
|
||||
touchCallback: (FlTouchEvent event, pieTouchResponse) {
|
||||
setState(() {
|
||||
if (!event.isInterestedForInteractions ||
|
||||
pieTouchResponse == null ||
|
||||
pieTouchResponse.touchedSection == null) {
|
||||
touchedIndex = -1;
|
||||
return;
|
||||
}
|
||||
touchedIndex = pieTouchResponse
|
||||
.touchedSection!.touchedSectionIndex;
|
||||
});
|
||||
},
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
sectionsSpace: 0,
|
||||
centerSpaceRadius: 40,
|
||||
sections: showingSections(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Indicator(
|
||||
color: AppColors.contentColorBlue,
|
||||
text: 'First',
|
||||
isSquare: true,
|
||||
),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Indicator(
|
||||
color: AppColors.contentColorYellow,
|
||||
text: 'Second',
|
||||
isSquare: true,
|
||||
),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Indicator(
|
||||
color: AppColors.contentColorPurple,
|
||||
text: 'Third',
|
||||
isSquare: true,
|
||||
),
|
||||
SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
Indicator(
|
||||
color: AppColors.contentColorGreen,
|
||||
text: 'Fourth',
|
||||
isSquare: true,
|
||||
),
|
||||
SizedBox(
|
||||
height: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
width: 28,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<PieChartSectionData> showingSections() {
|
||||
return List.generate(4, (i) {
|
||||
final isTouched = i == touchedIndex;
|
||||
final fontSize = isTouched ? 25.0 : 16.0;
|
||||
final radius = isTouched ? 60.0 : 50.0;
|
||||
const shadows = [Shadow(color: Colors.black, blurRadius: 2)];
|
||||
switch (i) {
|
||||
case 0:
|
||||
return PieChartSectionData(
|
||||
color: AppColors.contentColorBlue,
|
||||
value: 40,
|
||||
title: '40%',
|
||||
radius: radius,
|
||||
titleStyle: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.mainTextColor1,
|
||||
shadows: shadows,
|
||||
),
|
||||
);
|
||||
case 1:
|
||||
return PieChartSectionData(
|
||||
color: AppColors.contentColorYellow,
|
||||
value: 30,
|
||||
title: '30%',
|
||||
radius: radius,
|
||||
titleStyle: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.mainTextColor1,
|
||||
shadows: shadows,
|
||||
),
|
||||
);
|
||||
case 2:
|
||||
return PieChartSectionData(
|
||||
color: AppColors.contentColorPurple,
|
||||
value: 15,
|
||||
title: '15%',
|
||||
radius: radius,
|
||||
titleStyle: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.mainTextColor1,
|
||||
shadows: shadows,
|
||||
),
|
||||
);
|
||||
case 3:
|
||||
return PieChartSectionData(
|
||||
color: AppColors.contentColorGreen,
|
||||
value: 15,
|
||||
title: '15%',
|
||||
radius: radius,
|
||||
titleStyle: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: AppColors.mainTextColor1,
|
||||
shadows: shadows,
|
||||
),
|
||||
);
|
||||
default:
|
||||
throw Error();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
181
example/lib/presentation/samples/pie/pie_chart_sample3.dart
Normal file
181
example/lib/presentation/samples/pie/pie_chart_sample3.dart
Normal file
@@ -0,0 +1,181 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class PieChartSample3 extends StatefulWidget {
|
||||
const PieChartSample3({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => PieChartSample3State();
|
||||
}
|
||||
|
||||
class PieChartSample3State extends State {
|
||||
int touchedIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AspectRatio(
|
||||
aspectRatio: 1.3,
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: PieChart(
|
||||
PieChartData(
|
||||
pieTouchData: PieTouchData(
|
||||
touchCallback: (FlTouchEvent event, pieTouchResponse) {
|
||||
setState(() {
|
||||
if (!event.isInterestedForInteractions ||
|
||||
pieTouchResponse == null ||
|
||||
pieTouchResponse.touchedSection == null) {
|
||||
touchedIndex = -1;
|
||||
return;
|
||||
}
|
||||
touchedIndex =
|
||||
pieTouchResponse.touchedSection!.touchedSectionIndex;
|
||||
});
|
||||
},
|
||||
),
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
sectionsSpace: 0,
|
||||
centerSpaceRadius: 0,
|
||||
sections: showingSections(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<PieChartSectionData> showingSections() {
|
||||
return List.generate(4, (i) {
|
||||
final isTouched = i == touchedIndex;
|
||||
final fontSize = isTouched ? 20.0 : 16.0;
|
||||
final radius = isTouched ? 110.0 : 100.0;
|
||||
final widgetSize = isTouched ? 55.0 : 40.0;
|
||||
const shadows = [Shadow(color: Colors.black, blurRadius: 2)];
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
return PieChartSectionData(
|
||||
color: AppColors.contentColorBlue,
|
||||
value: 40,
|
||||
title: '40%',
|
||||
radius: radius,
|
||||
titleStyle: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: const Color(0xffffffff),
|
||||
shadows: shadows,
|
||||
),
|
||||
badgeWidget: _Badge(
|
||||
'assets/icons/ophthalmology-svgrepo-com.svg',
|
||||
size: widgetSize,
|
||||
borderColor: AppColors.contentColorBlack,
|
||||
),
|
||||
badgePositionPercentageOffset: .98,
|
||||
);
|
||||
case 1:
|
||||
return PieChartSectionData(
|
||||
color: AppColors.contentColorYellow,
|
||||
value: 30,
|
||||
title: '30%',
|
||||
radius: radius,
|
||||
titleStyle: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: const Color(0xffffffff),
|
||||
shadows: shadows,
|
||||
),
|
||||
badgeWidget: _Badge(
|
||||
'assets/icons/librarian-svgrepo-com.svg',
|
||||
size: widgetSize,
|
||||
borderColor: AppColors.contentColorBlack,
|
||||
),
|
||||
badgePositionPercentageOffset: .98,
|
||||
);
|
||||
case 2:
|
||||
return PieChartSectionData(
|
||||
color: AppColors.contentColorPurple,
|
||||
value: 16,
|
||||
title: '16%',
|
||||
radius: radius,
|
||||
titleStyle: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: const Color(0xffffffff),
|
||||
shadows: shadows,
|
||||
),
|
||||
badgeWidget: _Badge(
|
||||
'assets/icons/fitness-svgrepo-com.svg',
|
||||
size: widgetSize,
|
||||
borderColor: AppColors.contentColorBlack,
|
||||
),
|
||||
badgePositionPercentageOffset: .98,
|
||||
);
|
||||
case 3:
|
||||
return PieChartSectionData(
|
||||
color: AppColors.contentColorGreen,
|
||||
value: 15,
|
||||
title: '15%',
|
||||
radius: radius,
|
||||
titleStyle: TextStyle(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: const Color(0xffffffff),
|
||||
shadows: shadows,
|
||||
),
|
||||
badgeWidget: _Badge(
|
||||
'assets/icons/worker-svgrepo-com.svg',
|
||||
size: widgetSize,
|
||||
borderColor: AppColors.contentColorBlack,
|
||||
),
|
||||
badgePositionPercentageOffset: .98,
|
||||
);
|
||||
default:
|
||||
throw Exception('Oh no');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _Badge extends StatelessWidget {
|
||||
const _Badge(
|
||||
this.svgAsset, {
|
||||
required this.size,
|
||||
required this.borderColor,
|
||||
});
|
||||
final String svgAsset;
|
||||
final double size;
|
||||
final Color borderColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedContainer(
|
||||
duration: PieChart.defaultDuration,
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: borderColor,
|
||||
width: 2,
|
||||
),
|
||||
boxShadow: <BoxShadow>[
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: .5),
|
||||
offset: const Offset(3, 3),
|
||||
blurRadius: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: EdgeInsets.all(size * .15),
|
||||
child: Center(
|
||||
child: SvgPicture.asset(
|
||||
svgAsset,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
285
example/lib/presentation/samples/radar/radar_chart_sample1.dart
Normal file
285
example/lib/presentation/samples/radar/radar_chart_sample1.dart
Normal file
@@ -0,0 +1,285 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/util/extensions/color_extensions.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RadarChartSample1 extends StatefulWidget {
|
||||
RadarChartSample1({super.key});
|
||||
|
||||
final gridColor = AppColors.contentColorPurple.lighten(80);
|
||||
final titleColor = AppColors.contentColorPurple.lighten(80);
|
||||
final fashionColor = AppColors.contentColorRed;
|
||||
final artColor = AppColors.contentColorCyan;
|
||||
final boxingColor = AppColors.contentColorGreen;
|
||||
final entertainmentColor = AppColors.contentColorWhite;
|
||||
final offRoadColor = AppColors.contentColorYellow;
|
||||
|
||||
@override
|
||||
State<RadarChartSample1> createState() => _RadarChartSample1State();
|
||||
}
|
||||
|
||||
class _RadarChartSample1State extends State<RadarChartSample1> {
|
||||
int selectedDataSetIndex = -1;
|
||||
double angleValue = 0;
|
||||
bool relativeAngleMode = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'Title configuration',
|
||||
style: TextStyle(
|
||||
color: AppColors.mainTextColor2,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Text(
|
||||
'Angle',
|
||||
style: TextStyle(
|
||||
color: AppColors.mainTextColor2,
|
||||
),
|
||||
),
|
||||
Slider(
|
||||
value: angleValue,
|
||||
max: 360,
|
||||
onChanged: (double value) => setState(() => angleValue = value),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: relativeAngleMode,
|
||||
onChanged: (v) => setState(() => relativeAngleMode = v!),
|
||||
),
|
||||
const Text('Relative'),
|
||||
],
|
||||
),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedDataSetIndex = -1;
|
||||
});
|
||||
},
|
||||
child: Text(
|
||||
'Categories'.toUpperCase(),
|
||||
style: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w300,
|
||||
color: AppColors.mainTextColor1,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: rawDataSets()
|
||||
.asMap()
|
||||
.map((index, value) {
|
||||
final isSelected = index == selectedDataSetIndex;
|
||||
return MapEntry(
|
||||
index,
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
selectedDataSetIndex = index;
|
||||
});
|
||||
},
|
||||
child: AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
margin: const EdgeInsets.symmetric(vertical: 2),
|
||||
height: 26,
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? AppColors.pageBackground
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(46),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 6,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AnimatedContainer(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
curve: Curves.easeInToLinear,
|
||||
padding: EdgeInsets.all(isSelected ? 8 : 6),
|
||||
decoration: BoxDecoration(
|
||||
color: value.color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
AnimatedDefaultTextStyle(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInToLinear,
|
||||
style: TextStyle(
|
||||
color:
|
||||
isSelected ? value.color : widget.gridColor,
|
||||
),
|
||||
child: Text(value.title),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
})
|
||||
.values
|
||||
.toList(),
|
||||
),
|
||||
AspectRatio(
|
||||
aspectRatio: 1.3,
|
||||
child: RadarChart(
|
||||
RadarChartData(
|
||||
radarTouchData: RadarTouchData(
|
||||
touchCallback: (FlTouchEvent event, response) {
|
||||
if (!event.isInterestedForInteractions) {
|
||||
setState(() {
|
||||
selectedDataSetIndex = -1;
|
||||
});
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
selectedDataSetIndex =
|
||||
response?.touchedSpot?.touchedDataSetIndex ?? -1;
|
||||
});
|
||||
},
|
||||
),
|
||||
dataSets: showingDataSets(),
|
||||
radarBackgroundColor: Colors.transparent,
|
||||
borderData: FlBorderData(show: false),
|
||||
radarBorderData: const BorderSide(color: Colors.transparent),
|
||||
titlePositionPercentageOffset: 0.2,
|
||||
titleTextStyle:
|
||||
TextStyle(color: widget.titleColor, fontSize: 14),
|
||||
getTitle: (index, angle) {
|
||||
final usedAngle =
|
||||
relativeAngleMode ? angle + angleValue : angleValue;
|
||||
switch (index) {
|
||||
case 0:
|
||||
return RadarChartTitle(
|
||||
text: 'Mobile or Tablet',
|
||||
angle: usedAngle,
|
||||
);
|
||||
case 2:
|
||||
return RadarChartTitle(
|
||||
text: 'Desktop',
|
||||
angle: usedAngle,
|
||||
);
|
||||
case 1:
|
||||
return RadarChartTitle(text: 'TV', angle: usedAngle);
|
||||
default:
|
||||
return const RadarChartTitle(text: '');
|
||||
}
|
||||
},
|
||||
tickCount: 1,
|
||||
ticksTextStyle:
|
||||
const TextStyle(color: Colors.transparent, fontSize: 10),
|
||||
tickBorderData: const BorderSide(color: Colors.transparent),
|
||||
gridBorderData: BorderSide(color: widget.gridColor, width: 2),
|
||||
),
|
||||
duration: const Duration(milliseconds: 400),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<RadarDataSet> showingDataSets() {
|
||||
return rawDataSets().asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final rawDataSet = entry.value;
|
||||
|
||||
final isSelected = index == selectedDataSetIndex
|
||||
? true
|
||||
: selectedDataSetIndex == -1
|
||||
? true
|
||||
: false;
|
||||
|
||||
return RadarDataSet(
|
||||
fillColor: isSelected
|
||||
? rawDataSet.color.withValues(alpha: 0.2)
|
||||
: rawDataSet.color.withValues(alpha: 0.05),
|
||||
borderColor: isSelected
|
||||
? rawDataSet.color
|
||||
: rawDataSet.color.withValues(alpha: 0.25),
|
||||
entryRadius: isSelected ? 3 : 2,
|
||||
dataEntries:
|
||||
rawDataSet.values.map((e) => RadarEntry(value: e)).toList(),
|
||||
borderWidth: isSelected ? 2.3 : 2,
|
||||
);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
List<RawDataSet> rawDataSets() {
|
||||
return [
|
||||
RawDataSet(
|
||||
title: 'Fashion',
|
||||
color: widget.fashionColor,
|
||||
values: [
|
||||
300,
|
||||
50,
|
||||
250,
|
||||
],
|
||||
),
|
||||
RawDataSet(
|
||||
title: 'Art & Tech',
|
||||
color: widget.artColor,
|
||||
values: [
|
||||
250,
|
||||
100,
|
||||
200,
|
||||
],
|
||||
),
|
||||
RawDataSet(
|
||||
title: 'Entertainment',
|
||||
color: widget.entertainmentColor,
|
||||
values: [
|
||||
200,
|
||||
150,
|
||||
50,
|
||||
],
|
||||
),
|
||||
RawDataSet(
|
||||
title: 'Off-road Vehicle',
|
||||
color: widget.offRoadColor,
|
||||
values: [
|
||||
150,
|
||||
200,
|
||||
150,
|
||||
],
|
||||
),
|
||||
RawDataSet(
|
||||
title: 'Boxing',
|
||||
color: widget.boxingColor,
|
||||
values: [
|
||||
100,
|
||||
250,
|
||||
100,
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class RawDataSet {
|
||||
RawDataSet({
|
||||
required this.title,
|
||||
required this.color,
|
||||
required this.values,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final Color color;
|
||||
final List<double> values;
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ScatterChartSample1 extends StatefulWidget {
|
||||
ScatterChartSample1({super.key});
|
||||
|
||||
final blue1 = AppColors.contentColorBlue.withValues(alpha: 0.5);
|
||||
final blue2 = AppColors.contentColorBlue;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => ScatterChartSample1State();
|
||||
}
|
||||
|
||||
class ScatterChartSample1State extends State<ScatterChartSample1> {
|
||||
final maxX = 50.0;
|
||||
final maxY = 50.0;
|
||||
final radius = 8.0;
|
||||
|
||||
bool showFlutter = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
showFlutter = !showFlutter;
|
||||
});
|
||||
},
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: ScatterChart(
|
||||
ScatterChartData(
|
||||
scatterSpots: showFlutter ? flutterLogoData() : randomData(),
|
||||
minX: 0,
|
||||
maxX: maxX,
|
||||
minY: 0,
|
||||
maxY: maxY,
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
gridData: const FlGridData(
|
||||
show: false,
|
||||
),
|
||||
titlesData: const FlTitlesData(
|
||||
show: false,
|
||||
),
|
||||
scatterTouchData: ScatterTouchData(
|
||||
enabled: false,
|
||||
),
|
||||
),
|
||||
duration: const Duration(milliseconds: 600),
|
||||
curve: Curves.fastOutSlowIn,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<ScatterSpot> flutterLogoData() {
|
||||
return [
|
||||
/// section 1
|
||||
ScatterSpot(
|
||||
20,
|
||||
14.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
20,
|
||||
14.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
22,
|
||||
16.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
24,
|
||||
18.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
22,
|
||||
12.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
24,
|
||||
14.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
26,
|
||||
16.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
24,
|
||||
10.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
26,
|
||||
12.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
28,
|
||||
14.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
26,
|
||||
8.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
28,
|
||||
10.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
30,
|
||||
12.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
28,
|
||||
6.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
30,
|
||||
8.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
32,
|
||||
10.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
30,
|
||||
4.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
32,
|
||||
6.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
34,
|
||||
8.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
34,
|
||||
4.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
36,
|
||||
6.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
38,
|
||||
4.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue1, radius: radius),
|
||||
),
|
||||
|
||||
/// section 2
|
||||
ScatterSpot(
|
||||
20,
|
||||
14.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
22,
|
||||
12.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
24,
|
||||
10.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
22,
|
||||
16.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
24,
|
||||
14.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
26,
|
||||
12.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
24,
|
||||
18.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
26,
|
||||
16.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
28,
|
||||
14.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
26,
|
||||
20.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
28,
|
||||
18.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
30,
|
||||
16.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
28,
|
||||
22.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
30,
|
||||
20.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
32,
|
||||
18.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
30,
|
||||
24.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
32,
|
||||
22.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
34,
|
||||
20.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
34,
|
||||
24.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
36,
|
||||
22.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
38,
|
||||
24.5,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
/// section 3
|
||||
ScatterSpot(
|
||||
10,
|
||||
25,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
12,
|
||||
23,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
14,
|
||||
21,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
12,
|
||||
27,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
14,
|
||||
25,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
16,
|
||||
23,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
14,
|
||||
29,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
16,
|
||||
27,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
18,
|
||||
25,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
16,
|
||||
31,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
18,
|
||||
29,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
20,
|
||||
27,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
18,
|
||||
33,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
20,
|
||||
31,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
22,
|
||||
29,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
20,
|
||||
35,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
22,
|
||||
33,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
24,
|
||||
31,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
22,
|
||||
37,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
24,
|
||||
35,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
26,
|
||||
33,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
24,
|
||||
39,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
26,
|
||||
37,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
28,
|
||||
35,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
26,
|
||||
41,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
28,
|
||||
39,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
30,
|
||||
37,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
28,
|
||||
43,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
30,
|
||||
41,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
32,
|
||||
39,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
30,
|
||||
45,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
32,
|
||||
43,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
34,
|
||||
41,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
34,
|
||||
45,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
ScatterSpot(
|
||||
36,
|
||||
43,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
|
||||
ScatterSpot(
|
||||
38,
|
||||
45,
|
||||
dotPainter: FlDotCirclePainter(color: widget.blue2, radius: radius),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
List<ScatterSpot> randomData() {
|
||||
const blue1Count = 21;
|
||||
const blue2Count = 57;
|
||||
return List.generate(blue1Count + blue2Count, (i) {
|
||||
Color color;
|
||||
if (i < blue1Count) {
|
||||
color = widget.blue1;
|
||||
} else {
|
||||
color = widget.blue2;
|
||||
}
|
||||
|
||||
return ScatterSpot(
|
||||
(Random().nextDouble() * (maxX - 8)) + 4,
|
||||
(Random().nextDouble() * (maxY - 8)) + 4,
|
||||
dotPainter: FlDotCirclePainter(
|
||||
color: color,
|
||||
radius: (Random().nextDouble() * 16) + 4,
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ScatterChartSample2 extends StatefulWidget {
|
||||
const ScatterChartSample2({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ScatterChartSample2State();
|
||||
}
|
||||
|
||||
class _ScatterChartSample2State extends State {
|
||||
int touchedIndex = -1;
|
||||
|
||||
Color greyColor = Colors.grey;
|
||||
final _availableColors = [
|
||||
AppColors.contentColorGreen,
|
||||
AppColors.contentColorYellow,
|
||||
AppColors.contentColorPink,
|
||||
AppColors.contentColorOrange,
|
||||
AppColors.contentColorPurple,
|
||||
AppColors.contentColorBlue,
|
||||
AppColors.contentColorRed,
|
||||
AppColors.contentColorCyan,
|
||||
AppColors.contentColorBlue,
|
||||
AppColors.contentColorGreen,
|
||||
AppColors.contentColorPink,
|
||||
];
|
||||
|
||||
List<int> selectedSpots = [];
|
||||
|
||||
PainterType _currentPaintType = PainterType.circle;
|
||||
|
||||
static FlDotPainter _getPaint(PainterType type, double size, Color color) {
|
||||
switch (type) {
|
||||
case PainterType.circle:
|
||||
return FlDotCirclePainter(
|
||||
color: color,
|
||||
radius: size,
|
||||
);
|
||||
case PainterType.square:
|
||||
return FlDotSquarePainter(
|
||||
color: color,
|
||||
size: size * 2,
|
||||
strokeWidth: 0,
|
||||
);
|
||||
case PainterType.cross:
|
||||
return FlDotCrossPainter(
|
||||
color: color,
|
||||
size: size * 2,
|
||||
width: max(size / 5, 2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// (x, y, size)
|
||||
final data = [
|
||||
(4.0, 4.0, 4.0),
|
||||
(2.0, 5.0, 12.0),
|
||||
(4.0, 5.0, 8.0),
|
||||
(8.0, 6.0, 20.0),
|
||||
(5.0, 7.0, 14.0),
|
||||
(7.0, 2.0, 18.0),
|
||||
(3.0, 2.0, 36.0),
|
||||
(2.0, 8.0, 22.0),
|
||||
(8.0, 8.0, 32.0),
|
||||
(5.0, 2.5, 24.0),
|
||||
(3.0, 7.0, 18.0),
|
||||
];
|
||||
return AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Stack(
|
||||
children: [
|
||||
ScatterChart(
|
||||
ScatterChartData(
|
||||
scatterSpots: data.asMap().entries.map((e) {
|
||||
final index = e.key;
|
||||
final (double x, double y, double size) = e.value;
|
||||
return ScatterSpot(
|
||||
x,
|
||||
y,
|
||||
dotPainter: _getPaint(
|
||||
_currentPaintType,
|
||||
size,
|
||||
selectedSpots.contains(index)
|
||||
? _availableColors[index % _availableColors.length]
|
||||
: AppColors.contentColorWhite.withValues(alpha: 0.5),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
minX: 0,
|
||||
maxX: 10,
|
||||
minY: 0,
|
||||
maxY: 10,
|
||||
borderData: FlBorderData(
|
||||
show: false,
|
||||
),
|
||||
gridData: FlGridData(
|
||||
show: true,
|
||||
drawHorizontalLine: true,
|
||||
checkToShowHorizontalLine: (value) => true,
|
||||
getDrawingHorizontalLine: (value) => const FlLine(
|
||||
color: AppColors.gridLinesColor,
|
||||
),
|
||||
drawVerticalLine: true,
|
||||
checkToShowVerticalLine: (value) => true,
|
||||
getDrawingVerticalLine: (value) => const FlLine(
|
||||
color: AppColors.gridLinesColor,
|
||||
),
|
||||
),
|
||||
titlesData: const FlTitlesData(
|
||||
show: false,
|
||||
),
|
||||
showingTooltipIndicators: selectedSpots,
|
||||
scatterTouchData: ScatterTouchData(
|
||||
enabled: true,
|
||||
handleBuiltInTouches: false,
|
||||
mouseCursorResolver:
|
||||
(FlTouchEvent touchEvent, ScatterTouchResponse? response) {
|
||||
return response == null || response.touchedSpot == null
|
||||
? MouseCursor.defer
|
||||
: SystemMouseCursors.click;
|
||||
},
|
||||
touchTooltipData: ScatterTouchTooltipData(
|
||||
getTooltipColor: (ScatterSpot touchedBarSpot) {
|
||||
return touchedBarSpot.dotPainter.mainColor;
|
||||
},
|
||||
getTooltipItems: (ScatterSpot touchedBarSpot) {
|
||||
final bool isBgDark =
|
||||
switch ((touchedBarSpot.x, touchedBarSpot.y)) {
|
||||
(4.0, 4.0) => false,
|
||||
(2.0, 5.0) => false,
|
||||
(4.0, 5.0) => true,
|
||||
(8.0, 6.0) => true,
|
||||
(5.0, 7.0) => true,
|
||||
(7.0, 2.0) => true,
|
||||
(3.0, 2.0) => true,
|
||||
(2.0, 8.0) => false,
|
||||
(8.0, 8.0) => true,
|
||||
(5.0, 2.5) => false,
|
||||
(3.0, 7.0) => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
final color1 = isBgDark ? Colors.grey[100] : Colors.black87;
|
||||
final color2 = isBgDark ? Colors.white : Colors.black;
|
||||
return ScatterTooltipItem(
|
||||
'X: ',
|
||||
textStyle: TextStyle(
|
||||
height: 1.2,
|
||||
color: color1,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
bottomMargin: 10,
|
||||
children: [
|
||||
TextSpan(
|
||||
text: '${touchedBarSpot.x.toInt()} \n',
|
||||
style: TextStyle(
|
||||
color: color2,
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: 'Y: ',
|
||||
style: TextStyle(
|
||||
height: 1.2,
|
||||
color: color1,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: touchedBarSpot.y.toInt().toString(),
|
||||
style: TextStyle(
|
||||
color: color2,
|
||||
fontStyle: FontStyle.normal,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
touchCallback:
|
||||
(FlTouchEvent event, ScatterTouchResponse? touchResponse) {
|
||||
if (touchResponse == null ||
|
||||
touchResponse.touchedSpot == null) {
|
||||
return;
|
||||
}
|
||||
if (event is FlTapUpEvent) {
|
||||
final sectionIndex = touchResponse.touchedSpot!.spotIndex;
|
||||
setState(() {
|
||||
if (selectedSpots.contains(sectionIndex)) {
|
||||
selectedSpots.remove(sectionIndex);
|
||||
} else {
|
||||
selectedSpots.add(sectionIndex);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: DropdownButton(
|
||||
value: _currentPaintType,
|
||||
items: PainterType.values
|
||||
.map((e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(e.name),
|
||||
))
|
||||
.toList(),
|
||||
onChanged: (PainterType? value) {
|
||||
setState(() {
|
||||
_currentPaintType = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum PainterType {
|
||||
circle,
|
||||
square,
|
||||
cross,
|
||||
}
|
||||
54
example/lib/presentation/widgets/chart_holder.dart
Normal file
54
example/lib/presentation/widgets/chart_holder.dart
Normal file
@@ -0,0 +1,54 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/presentation/samples/chart_sample.dart';
|
||||
import 'package:fl_chart_app/util/app_utils.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChartHolder extends StatelessWidget {
|
||||
final ChartSample chartSample;
|
||||
|
||||
const ChartHolder({
|
||||
super.key,
|
||||
required this.chartSample,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
chartSample.name,
|
||||
style: const TextStyle(
|
||||
color: AppColors.primary,
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
Expanded(child: Container()),
|
||||
IconButton(
|
||||
onPressed: () => AppUtils().tryToLaunchUrl(chartSample.url),
|
||||
icon: const Icon(
|
||||
Icons.code,
|
||||
color: AppColors.primary,
|
||||
),
|
||||
tooltip: 'Source code',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
Container(
|
||||
decoration: const BoxDecoration(
|
||||
color: AppColors.itemsBackground,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(AppDimens.defaultRadius)),
|
||||
),
|
||||
child: chartSample.builder(context),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_colors.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DownloadNativeAppButton extends StatelessWidget {
|
||||
const DownloadNativeAppButton({
|
||||
super.key,
|
||||
required this.onClose,
|
||||
required this.onDownload,
|
||||
});
|
||||
|
||||
final VoidCallback onClose;
|
||||
final VoidCallback onDownload;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.contentColorYellow,
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
),
|
||||
child: Material(
|
||||
color: Colors.transparent,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(999),
|
||||
onTap: onDownload,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const SizedBox(width: 4),
|
||||
const Icon(
|
||||
size: 28,
|
||||
Icons.download_for_offline,
|
||||
color: AppColors.contentColorBlack,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const Text(
|
||||
'Download Native App',
|
||||
style: TextStyle(
|
||||
color: AppColors.contentColorBlack,
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: onClose,
|
||||
icon: const Icon(
|
||||
size: 16,
|
||||
Icons.close,
|
||||
color: AppColors.contentColorBlack,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
44
example/lib/presentation/widgets/indicator.dart
Normal file
44
example/lib/presentation/widgets/indicator.dart
Normal file
@@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Indicator extends StatelessWidget {
|
||||
const Indicator({
|
||||
super.key,
|
||||
required this.color,
|
||||
required this.text,
|
||||
required this.isSquare,
|
||||
this.size = 16,
|
||||
this.textColor,
|
||||
});
|
||||
final Color color;
|
||||
final String text;
|
||||
final bool isSquare;
|
||||
final double size;
|
||||
final Color? textColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
shape: isSquare ? BoxShape.rectangle : BoxShape.circle,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: textColor,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
65
example/lib/presentation/widgets/legend_widget.dart
Normal file
65
example/lib/presentation/widgets/legend_widget.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LegendWidget extends StatelessWidget {
|
||||
const LegendWidget({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.color,
|
||||
});
|
||||
final String name;
|
||||
final Color color;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 10,
|
||||
height: 10,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
name,
|
||||
style: const TextStyle(
|
||||
color: Color(0xff757391),
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LegendsListWidget extends StatelessWidget {
|
||||
const LegendsListWidget({
|
||||
super.key,
|
||||
required this.legends,
|
||||
});
|
||||
final List<Legend> legends;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
spacing: 16,
|
||||
children: legends
|
||||
.map(
|
||||
(e) => LegendWidget(
|
||||
name: e.name,
|
||||
color: e.color,
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Legend {
|
||||
Legend(this.name, this.color);
|
||||
final String name;
|
||||
final Color color;
|
||||
}
|
||||
23
example/lib/urls.dart
Normal file
23
example/lib/urls.dart
Normal file
@@ -0,0 +1,23 @@
|
||||
import 'package:fl_chart_app/util/app_helper.dart';
|
||||
|
||||
class Urls {
|
||||
static const flChartUrl = 'https://flchart.dev';
|
||||
static const flChartGithubUrl = 'https://github.com/imaNNeo/fl_chart';
|
||||
|
||||
static String get aboutUrl => '$flChartUrl/about';
|
||||
|
||||
static String get downloadUrl => '$flChartUrl/download';
|
||||
|
||||
static String getChartSourceCodeUrl(ChartType chartType, int sampleNumber) {
|
||||
final chartDir = chartType.name.toLowerCase();
|
||||
return 'https://github.com/imaNNeo/fl_chart/blob/main/example/lib/presentation/samples/$chartDir/${chartDir}_chart_sample$sampleNumber.dart';
|
||||
}
|
||||
|
||||
static String getChartDocumentationUrl(ChartType chartType) {
|
||||
final chartDir = chartType.name.toLowerCase();
|
||||
return 'https://github.com/imaNNeo/fl_chart/blob/main/repo_files/documentations/${chartDir}_chart.md';
|
||||
}
|
||||
|
||||
static String getVersionReleaseUrl(String version) =>
|
||||
'$flChartGithubUrl/releases/tag/$version';
|
||||
}
|
||||
21
example/lib/util/app_helper.dart
Normal file
21
example/lib/util/app_helper.dart
Normal file
@@ -0,0 +1,21 @@
|
||||
import 'package:fl_chart_app/presentation/resources/app_resources.dart';
|
||||
import 'package:fl_chart_app/urls.dart';
|
||||
|
||||
enum ChartType { line, bar, pie, scatter, radar, candlestick }
|
||||
|
||||
extension ChartTypeExtension on ChartType {
|
||||
String get displayName => '$simpleName Chart';
|
||||
|
||||
String get simpleName => switch (this) {
|
||||
ChartType.line => 'Line',
|
||||
ChartType.bar => 'Bar',
|
||||
ChartType.pie => 'Pie',
|
||||
ChartType.scatter => 'Scatter',
|
||||
ChartType.radar => 'Radar',
|
||||
ChartType.candlestick => 'Candlestick',
|
||||
};
|
||||
|
||||
String get documentationUrl => Urls.getChartDocumentationUrl(this);
|
||||
|
||||
String get assetIcon => AppAssets.getChartIcon(this);
|
||||
}
|
||||
28
example/lib/util/app_utils.dart
Normal file
28
example/lib/util/app_utils.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AppUtils {
|
||||
factory AppUtils() {
|
||||
return _singleton;
|
||||
}
|
||||
|
||||
AppUtils._internal();
|
||||
static final AppUtils _singleton = AppUtils._internal();
|
||||
|
||||
double degreeToRadian(double degree) {
|
||||
return degree * math.pi / 180;
|
||||
}
|
||||
|
||||
double radianToDegree(double radian) {
|
||||
return radian * 180 / math.pi;
|
||||
}
|
||||
|
||||
Future<bool> tryToLaunchUrl(String url) async {
|
||||
final uri = Uri.parse(url);
|
||||
if (await canLaunchUrl(uri)) {
|
||||
return await launchUrl(uri);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
45
example/lib/util/csv_parser.dart
Normal file
45
example/lib/util/csv_parser.dart
Normal file
@@ -0,0 +1,45 @@
|
||||
class CsvParser {
|
||||
static List<List<String>> parse(String rawCsvData) {
|
||||
final lines =
|
||||
rawCsvData.split('\n').where((line) => line.isNotEmpty).toList();
|
||||
final headers = _parseCsvLine(lines.first);
|
||||
|
||||
return [
|
||||
headers,
|
||||
...lines.skip(1).map((line) => _parseCsvLine(line)),
|
||||
];
|
||||
}
|
||||
|
||||
static List<String> _parseCsvLine(String line) {
|
||||
final values = <String>[];
|
||||
final buffer = StringBuffer();
|
||||
bool insideQuotes = false;
|
||||
|
||||
for (int i = 0; i < line.length; i++) {
|
||||
final char = line[i];
|
||||
|
||||
if (char == '"') {
|
||||
if (insideQuotes && i + 1 < line.length && line[i + 1] == '"') {
|
||||
// Handle escaped quotes
|
||||
buffer.write('"');
|
||||
i++; // Skip the next quote
|
||||
} else {
|
||||
// Toggle the insideQuotes flag
|
||||
insideQuotes = !insideQuotes;
|
||||
}
|
||||
} else if (char == ',' && !insideQuotes) {
|
||||
// End of value
|
||||
values.add(buffer.toString());
|
||||
buffer.clear();
|
||||
} else {
|
||||
// Normal character
|
||||
buffer.write(char);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the last value
|
||||
values.add(buffer.toString());
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
46
example/lib/util/device_info.dart
Normal file
46
example/lib/util/device_info.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
enum FormFactorType { monitor, smallPhone, largePhone, tablet }
|
||||
|
||||
// Copied from https://github.com/gskinnerTeam/flutter-folio/blob/main/lib/_utils/device_info.dart
|
||||
class DeviceOS {
|
||||
// Syntax sugar, proxy the UniversalPlatform methods so our views can reference a single class
|
||||
static bool isIOS = UniversalPlatform.isIOS;
|
||||
static bool isAndroid = UniversalPlatform.isAndroid;
|
||||
static bool isMacOS = UniversalPlatform.isMacOS;
|
||||
static bool isLinux = UniversalPlatform.isLinux;
|
||||
static bool isWindows = UniversalPlatform.isWindows;
|
||||
|
||||
// Higher level device class abstractions (more syntax sugar for the views)
|
||||
static bool isWeb = kIsWeb;
|
||||
static bool get isDesktop => isWindows || isMacOS || isLinux;
|
||||
static bool get isMobile => isAndroid || isIOS;
|
||||
static bool get isDesktopOrWeb => isDesktop || isWeb;
|
||||
static bool get isMobileOrWeb => isMobile || isWeb;
|
||||
}
|
||||
|
||||
class DeviceScreen {
|
||||
// Get the device form factor as best we can.
|
||||
// Otherwise we will use the screen size to determine which class we fall into.
|
||||
static FormFactorType get(BuildContext context) {
|
||||
var shortestSide = MediaQuery.of(context).size.shortestSide;
|
||||
if (shortestSide <= 300) return FormFactorType.smallPhone;
|
||||
if (shortestSide <= 600) return FormFactorType.largePhone;
|
||||
if (shortestSide <= 900) return FormFactorType.tablet;
|
||||
return FormFactorType.monitor;
|
||||
}
|
||||
|
||||
// Shortcuts for various mobile device types
|
||||
static bool isPhone(BuildContext context) =>
|
||||
isSmallPhone(context) || isLargePhone(context);
|
||||
static bool isTablet(BuildContext context) =>
|
||||
get(context) == FormFactorType.tablet;
|
||||
static bool isMonitor(BuildContext context) =>
|
||||
get(context) == FormFactorType.monitor;
|
||||
static bool isSmallPhone(BuildContext context) =>
|
||||
get(context) == FormFactorType.smallPhone;
|
||||
static bool isLargePhone(BuildContext context) =>
|
||||
get(context) == FormFactorType.largePhone;
|
||||
}
|
||||
43
example/lib/util/extensions/color_extensions.dart
Normal file
43
example/lib/util/extensions/color_extensions.dart
Normal file
@@ -0,0 +1,43 @@
|
||||
import 'dart:ui';
|
||||
|
||||
extension ColorExtension on Color {
|
||||
/// Convert the color to a darken color based on the [percent]
|
||||
Color darken([int percent = 40]) {
|
||||
assert(1 <= percent && percent <= 100);
|
||||
final value = 1 - percent / 100;
|
||||
return Color.fromARGB(
|
||||
_floatToInt8(a),
|
||||
(_floatToInt8(r) * value).round(),
|
||||
(_floatToInt8(g) * value).round(),
|
||||
(_floatToInt8(b) * value).round(),
|
||||
);
|
||||
}
|
||||
|
||||
Color lighten([int percent = 40]) {
|
||||
assert(1 <= percent && percent <= 100);
|
||||
final value = percent / 100;
|
||||
return Color.fromARGB(
|
||||
_floatToInt8(a),
|
||||
(_floatToInt8(r) + ((255 - _floatToInt8(r)) * value)).round(),
|
||||
(_floatToInt8(g) + ((255 - _floatToInt8(g)) * value)).round(),
|
||||
(_floatToInt8(b) + ((255 - _floatToInt8(b)) * value)).round(),
|
||||
);
|
||||
}
|
||||
|
||||
Color avg(Color other) {
|
||||
final red = (_floatToInt8(r) + _floatToInt8(other.r)) ~/ 2;
|
||||
final green = (_floatToInt8(g) + _floatToInt8(other.g)) ~/ 2;
|
||||
final blue = (_floatToInt8(b) + _floatToInt8(other.b)) ~/ 2;
|
||||
final alpha = (_floatToInt8(a) + _floatToInt8(other.a)) ~/ 2;
|
||||
return Color.fromARGB(alpha, red, green, blue);
|
||||
}
|
||||
|
||||
// Int color components were deprecated in Flutter 3.27.0.
|
||||
// This method is used to convert the new double color components to the
|
||||
// old int color components.
|
||||
//
|
||||
// Taken from the Color class.
|
||||
int _floatToInt8(double x) {
|
||||
return (x * 255.0).round() & 0xff;
|
||||
}
|
||||
}
|
||||
3
example/lib/util/extensions/iterable_extensions.dart
Normal file
3
example/lib/util/extensions/iterable_extensions.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
extension IterableToMapExtension<K, V> on Iterable<MapEntry<K, V>> {
|
||||
Map<K, V> get asMap => Map.fromEntries(this);
|
||||
}
|
||||
3
example/lib/util/extensions/list_extensions.dart
Normal file
3
example/lib/util/extensions/list_extensions.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
extension ListToMapExtension<K, V> on List<MapEntry<K, V>> {
|
||||
Map<K, V> get asMap => Map.fromEntries(this);
|
||||
}
|
||||
Reference in New Issue
Block a user